lcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/skills/lcp-custom-field/SKILL.md +205 -0
- data/.claude/skills/lcp-getting-started/SKILL.md +332 -0
- data/.claude/skills/lcp-host-binding/SKILL.md +287 -0
- data/.claude/skills/lcp-model/SKILL.md +185 -0
- data/.claude/skills/lcp-permissions/SKILL.md +176 -0
- data/.claude/skills/lcp-presenter/SKILL.md +194 -0
- data/.claude/skills/lcp-workflow/SKILL.md +281 -0
- data/CHANGELOG.md +69 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +17 -0
- data/app/assets/javascripts/lcp_ruby/application.js +58 -0
- data/app/assets/javascripts/lcp_ruby/controllers/advanced_filter_controller.js +1521 -0
- data/app/assets/javascripts/lcp_ruby/controllers/approval_actions_controller.js +24 -0
- data/app/assets/javascripts/lcp_ruby/controllers/array_input_controller.js +156 -0
- data/app/assets/javascripts/lcp_ruby/controllers/batch_select_controller.js +405 -0
- data/app/assets/javascripts/lcp_ruby/controllers/cascading_selects_controller.js +436 -0
- data/app/assets/javascripts/lcp_ruby/controllers/char_counter_controller.js +28 -0
- data/app/assets/javascripts/lcp_ruby/controllers/clipboard_controller.js +62 -0
- data/app/assets/javascripts/lcp_ruby/controllers/conditional_rendering_controller.js +178 -0
- data/app/assets/javascripts/lcp_ruby/controllers/confirm_dialog_controller.js +131 -0
- data/app/assets/javascripts/lcp_ruby/controllers/custom_fields_manage_controller.js +80 -0
- data/app/assets/javascripts/lcp_ruby/controllers/dialog_controller.js +421 -0
- data/app/assets/javascripts/lcp_ruby/controllers/dialog_nested_controller.js +182 -0
- data/app/assets/javascripts/lcp_ruby/controllers/direct_upload_controller.js +100 -0
- data/app/assets/javascripts/lcp_ruby/controllers/drawer_controller.js +205 -0
- data/app/assets/javascripts/lcp_ruby/controllers/dropdown_controller.js +160 -0
- data/app/assets/javascripts/lcp_ruby/controllers/export_dialog_controller.js +15 -0
- data/app/assets/javascripts/lcp_ruby/controllers/field_picker_controller.js +290 -0
- data/app/assets/javascripts/lcp_ruby/controllers/file_upload_controller.js +135 -0
- data/app/assets/javascripts/lcp_ruby/controllers/filter_auto_submit_controller.js +46 -0
- data/app/assets/javascripts/lcp_ruby/controllers/filter_dirty_controller.js +94 -0
- data/app/assets/javascripts/lcp_ruby/controllers/form_actions_controller.js +68 -0
- data/app/assets/javascripts/lcp_ruby/controllers/form_handling_controller.js +67 -0
- data/app/assets/javascripts/lcp_ruby/controllers/import_dialog_controller.js +142 -0
- data/app/assets/javascripts/lcp_ruby/controllers/import_mapper_controller.js +322 -0
- data/app/assets/javascripts/lcp_ruby/controllers/index_sortable_controller.js +388 -0
- data/app/assets/javascripts/lcp_ruby/controllers/inline_create_controller.js +137 -0
- data/app/assets/javascripts/lcp_ruby/controllers/kanban_board_controller.js +290 -0
- data/app/assets/javascripts/lcp_ruby/controllers/menu_controller.js +19 -0
- data/app/assets/javascripts/lcp_ruby/controllers/nested_forms_controller.js +261 -0
- data/app/assets/javascripts/lcp_ruby/controllers/responsive_sidebar_controller.js +161 -0
- data/app/assets/javascripts/lcp_ruby/controllers/responsive_top_nav_controller.js +394 -0
- data/app/assets/javascripts/lcp_ruby/controllers/saved_filters_controller.js +129 -0
- data/app/assets/javascripts/lcp_ruby/controllers/search_controller.js +82 -0
- data/app/assets/javascripts/lcp_ruby/controllers/selection_controller.js +33 -0
- data/app/assets/javascripts/lcp_ruby/controllers/sidebar_controller.js +83 -0
- data/app/assets/javascripts/lcp_ruby/controllers/sidebar_toggle_controller.js +66 -0
- data/app/assets/javascripts/lcp_ruby/controllers/slider_controller.js +26 -0
- data/app/assets/javascripts/lcp_ruby/controllers/submenu_controller.js +55 -0
- data/app/assets/javascripts/lcp_ruby/controllers/tom_select_controller.js +168 -0
- data/app/assets/javascripts/lcp_ruby/controllers/tree_index_controller.js +106 -0
- data/app/assets/javascripts/lcp_ruby/controllers/tree_reparent_controller.js +182 -0
- data/app/assets/javascripts/lcp_ruby/controllers/tree_select_controller.js +119 -0
- data/app/assets/javascripts/lcp_ruby/controllers/ui_components_controller.js +135 -0
- data/app/assets/javascripts/lcp_ruby/controllers/zone_tabs_controller.js +66 -0
- data/app/assets/javascripts/lcp_ruby/dev_toolbar.js +494 -0
- data/app/assets/javascripts/lcp_ruby/i18n.js.erb +29 -0
- data/app/assets/javascripts/lcp_ruby/lucide_init.js +24 -0
- data/app/assets/javascripts/lcp_ruby/stimulus_bootstrap.js +7 -0
- data/app/assets/javascripts/lcp_ruby/utils.js +119 -0
- data/app/assets/javascripts/lcp_ruby/xhr_fetch.js +411 -0
- data/app/assets/stylesheets/lcp_ruby/application.css +1940 -0
- data/app/assets/stylesheets/lcp_ruby/dev_toolbar.css +355 -0
- data/app/controllers/concerns/lcp_ruby/dialog_rendering.rb +167 -0
- data/app/controllers/concerns/lcp_ruby/form_action_execution.rb +249 -0
- data/app/controllers/concerns/lcp_ruby/page_authorization.rb +105 -0
- data/app/controllers/concerns/lcp_ruby/zone_resolution.rb +199 -0
- data/app/controllers/lcp_ruby/actions_controller.rb +481 -0
- data/app/controllers/lcp_ruby/application_controller.rb +59 -0
- data/app/controllers/lcp_ruby/approval_tasks_controller.rb +98 -0
- data/app/controllers/lcp_ruby/auth/base_controller.rb +58 -0
- data/app/controllers/lcp_ruby/auth/callbacks_controller.rb +103 -0
- data/app/controllers/lcp_ruby/auth/passwords_controller.rb +15 -0
- data/app/controllers/lcp_ruby/auth/registrations_controller.rb +37 -0
- data/app/controllers/lcp_ruby/auth/sessions_controller.rb +112 -0
- data/app/controllers/lcp_ruby/custom_fields_controller.rb +370 -0
- data/app/controllers/lcp_ruby/dev_toolbar_controller.rb +197 -0
- data/app/controllers/lcp_ruby/dialogs_controller.rb +199 -0
- data/app/controllers/lcp_ruby/health_controller.rb +23 -0
- data/app/controllers/lcp_ruby/impersonation_controller.rb +32 -0
- data/app/controllers/lcp_ruby/landing_controller.rb +49 -0
- data/app/controllers/lcp_ruby/metrics_controller.rb +17 -0
- data/app/controllers/lcp_ruby/resources_controller.rb +2218 -0
- data/app/controllers/lcp_ruby/saved_filters_controller.rb +221 -0
- data/app/helpers/lcp_ruby/condition_helper.rb +87 -0
- data/app/helpers/lcp_ruby/dashboard_helper.rb +45 -0
- data/app/helpers/lcp_ruby/dev_toolbar_helper.rb +17 -0
- data/app/helpers/lcp_ruby/dialog_helper.rb +27 -0
- data/app/helpers/lcp_ruby/display/card_helper.rb +317 -0
- data/app/helpers/lcp_ruby/display_helper.rb +63 -0
- data/app/helpers/lcp_ruby/display_template_helper.rb +100 -0
- data/app/helpers/lcp_ruby/form_helper.rb +1020 -0
- data/app/helpers/lcp_ruby/grouped_query_helper.rb +107 -0
- data/app/helpers/lcp_ruby/i18n_payload_helper.rb +197 -0
- data/app/helpers/lcp_ruby/layout_helper.rb +832 -0
- data/app/helpers/lcp_ruby/link_through_helper.rb +29 -0
- data/app/helpers/lcp_ruby/oidc_button_helper.rb +59 -0
- data/app/helpers/lcp_ruby/search_helper.rb +31 -0
- data/app/helpers/lcp_ruby/tree_helper.rb +140 -0
- data/app/helpers/lcp_ruby/view_slot_helper.rb +33 -0
- data/app/models/lcp_ruby/user.rb +61 -0
- data/app/views/layouts/lcp_ruby/application.html.erb +168 -0
- data/app/views/layouts/lcp_ruby/auth.html.erb +170 -0
- data/app/views/lcp_ruby/auth/callbacks/failure.html.erb +18 -0
- data/app/views/lcp_ruby/auth/mailer/reset_password_instructions.html.erb +9 -0
- data/app/views/lcp_ruby/auth/passwords/edit.html.erb +34 -0
- data/app/views/lcp_ruby/auth/passwords/new.html.erb +24 -0
- data/app/views/lcp_ruby/auth/registrations/edit.html.erb +48 -0
- data/app/views/lcp_ruby/auth/registrations/new.html.erb +46 -0
- data/app/views/lcp_ruby/auth/sessions/new.html.erb +55 -0
- data/app/views/lcp_ruby/auth/shared/_links.html.erb +13 -0
- data/app/views/lcp_ruby/custom_fields/_manage_row.html.erb +82 -0
- data/app/views/lcp_ruby/custom_fields/manage.html.erb +47 -0
- data/app/views/lcp_ruby/dialogs/_dialog_composite_frame.html.erb +87 -0
- data/app/views/lcp_ruby/dialogs/_dialog_frame.html.erb +60 -0
- data/app/views/lcp_ruby/dialogs/_dialog_show_frame.html.erb +14 -0
- data/app/views/lcp_ruby/dialogs/_show_secret.html.erb +10 -0
- data/app/views/lcp_ruby/dialogs/_success.html.erb +1 -0
- data/app/views/lcp_ruby/errors/not_found.html.erb +5 -0
- data/app/views/lcp_ruby/menu_renderers/_user_menu.html.erb +51 -0
- data/app/views/lcp_ruby/menu_renderers/_user_menu_panel.html.erb +32 -0
- data/app/views/lcp_ruby/navigation/_auto_top.html.erb +16 -0
- data/app/views/lcp_ruby/navigation/_both_sidebar.html.erb +1 -0
- data/app/views/lcp_ruby/navigation/_both_top.html.erb +1 -0
- data/app/views/lcp_ruby/navigation/_impersonation_banner.html.erb +14 -0
- data/app/views/lcp_ruby/navigation/_item_content.html.erb +23 -0
- data/app/views/lcp_ruby/navigation/_link_or_button.html.erb +51 -0
- data/app/views/lcp_ruby/navigation/_mobile_header.html.erb +23 -0
- data/app/views/lcp_ruby/navigation/_panel_item.html.erb +32 -0
- data/app/views/lcp_ruby/navigation/_sidebar.html.erb +38 -0
- data/app/views/lcp_ruby/navigation/_sidebar_item.html.erb +38 -0
- data/app/views/lcp_ruby/navigation/_sidebar_toggle.html.erb +32 -0
- data/app/views/lcp_ruby/navigation/_top.html.erb +26 -0
- data/app/views/lcp_ruby/navigation/_top_item.html.erb +112 -0
- data/app/views/lcp_ruby/navigation/_widget_item.html.erb +14 -0
- data/app/views/lcp_ruby/resources/_action_button.html.erb +138 -0
- data/app/views/lcp_ruby/resources/_advanced_filter.html.erb +55 -0
- data/app/views/lcp_ruby/resources/_api_status_banner.html.erb +11 -0
- data/app/views/lcp_ruby/resources/_association_list.html.erb +132 -0
- data/app/views/lcp_ruby/resources/_audit_history.html.erb +66 -0
- data/app/views/lcp_ruby/resources/_batch_toolbar.html.erb +54 -0
- data/app/views/lcp_ruby/resources/_filter_form.html.erb +57 -0
- data/app/views/lcp_ruby/resources/_form.html.erb +73 -0
- data/app/views/lcp_ruby/resources/_form_action_button.html.erb +20 -0
- data/app/views/lcp_ruby/resources/_form_action_dropdown_item.html.erb +17 -0
- data/app/views/lcp_ruby/resources/_form_actions.html.erb +38 -0
- data/app/views/lcp_ruby/resources/_form_section.html.erb +235 -0
- data/app/views/lcp_ruby/resources/_grid_page.html.erb +17 -0
- data/app/views/lcp_ruby/resources/_grouped_index.html.erb +61 -0
- data/app/views/lcp_ruby/resources/_inline_create_form.html.erb +50 -0
- data/app/views/lcp_ruby/resources/_json_items_list.html.erb +77 -0
- data/app/views/lcp_ruby/resources/_json_nested_fields.html.erb +30 -0
- data/app/views/lcp_ruby/resources/_kanban_card.html.erb +31 -0
- data/app/views/lcp_ruby/resources/_kanban_card_body.html.erb +17 -0
- data/app/views/lcp_ruby/resources/_kanban_column.html.erb +33 -0
- data/app/views/lcp_ruby/resources/_kanban_column_header.html.erb +10 -0
- data/app/views/lcp_ruby/resources/_kanban_index.html.erb +17 -0
- data/app/views/lcp_ruby/resources/_nested_field_cell.html.erb +33 -0
- data/app/views/lcp_ruby/resources/_nested_fields.html.erb +26 -0
- data/app/views/lcp_ruby/resources/_nested_row_content.html.erb +65 -0
- data/app/views/lcp_ruby/resources/_scope_filters.html.erb +71 -0
- data/app/views/lcp_ruby/resources/_semantic_index_page.html.erb +162 -0
- data/app/views/lcp_ruby/resources/_semantic_page.html.erb +121 -0
- data/app/views/lcp_ruby/resources/_show_sections.html.erb +128 -0
- data/app/views/lcp_ruby/resources/_table_index.html.erb +169 -0
- data/app/views/lcp_ruby/resources/_tile_card.html.erb +26 -0
- data/app/views/lcp_ruby/resources/_tile_card_body.html.erb +19 -0
- data/app/views/lcp_ruby/resources/_tiles_index.html.erb +15 -0
- data/app/views/lcp_ruby/resources/_transition_button.html.erb +33 -0
- data/app/views/lcp_ruby/resources/_tree_index.html.erb +61 -0
- data/app/views/lcp_ruby/resources/_view_switcher.html.erb +24 -0
- data/app/views/lcp_ruby/resources/edit.html.erb +9 -0
- data/app/views/lcp_ruby/resources/index.html.erb +80 -0
- data/app/views/lcp_ruby/resources/new.html.erb +9 -0
- data/app/views/lcp_ruby/resources/show.html.erb +37 -0
- data/app/views/lcp_ruby/shared/_breadcrumbs.html.erb +15 -0
- data/app/views/lcp_ruby/shared/_custom_partial_error.html.erb +3 -0
- data/app/views/lcp_ruby/shared/_flash_messages.html.erb +19 -0
- data/app/views/lcp_ruby/slots/index/_advanced_filter.html.erb +3 -0
- data/app/views/lcp_ruby/slots/index/_collection_actions.html.erb +50 -0
- data/app/views/lcp_ruby/slots/index/_manage_all.html.erb +4 -0
- data/app/views/lcp_ruby/slots/index/_pagination_footer.html.erb +71 -0
- data/app/views/lcp_ruby/slots/index/_predefined_filters.html.erb +8 -0
- data/app/views/lcp_ruby/slots/index/_saved_filters.html.erb +87 -0
- data/app/views/lcp_ruby/slots/index/_search.html.erb +52 -0
- data/app/views/lcp_ruby/slots/index/_search_parameter.html.erb +72 -0
- data/app/views/lcp_ruby/slots/index/_sort_dropdown.html.erb +21 -0
- data/app/views/lcp_ruby/slots/index/_summary_bar.html.erb +22 -0
- data/app/views/lcp_ruby/slots/index/_view_switcher.html.erb +1 -0
- data/app/views/lcp_ruby/slots/show/_approval_status.html.erb +8 -0
- data/app/views/lcp_ruby/slots/show/_back_to_list.html.erb +1 -0
- data/app/views/lcp_ruby/slots/show/_copy_url.html.erb +5 -0
- data/app/views/lcp_ruby/slots/show/_single_actions.html.erb +4 -0
- data/app/views/lcp_ruby/slots/show/_view_switcher.html.erb +1 -0
- data/app/views/lcp_ruby/widgets/_approval_status.html.erb +150 -0
- data/app/views/lcp_ruby/widgets/_chart.html.erb +36 -0
- data/app/views/lcp_ruby/widgets/_embed.html.erb +13 -0
- data/app/views/lcp_ruby/widgets/_kpi_card.html.erb +29 -0
- data/app/views/lcp_ruby/widgets/_list.html.erb +17 -0
- data/app/views/lcp_ruby/widgets/_presenter_zone.html.erb +84 -0
- data/app/views/lcp_ruby/widgets/_record_show_zone.html.erb +11 -0
- data/app/views/lcp_ruby/widgets/_text.html.erb +3 -0
- data/app/views/lcp_ruby/widgets/_workflow_graph.html.erb +32 -0
- data/app/views/lcp_ruby/zones/_custom_zone.html.erb +16 -0
- data/app/views/lcp_ruby/zones/_error.html.erb +5 -0
- data/app/views/lcp_ruby/zones/_zone_frame.html.erb +25 -0
- data/app/views/lcp_ruby/zones/_zone_search.html.erb +20 -0
- data/config/locales/cs.yml +859 -0
- data/config/locales/en.yml +731 -0
- data/config/routes.rb +119 -0
- data/docs/README.md +225 -0
- data/docs/architecture.md +212 -0
- data/docs/feature-catalog.md +763 -0
- data/docs/feature-catalog.yml +20911 -0
- data/docs/getting-started.md +1187 -0
- data/docs/guides/action-buttons.md +537 -0
- data/docs/guides/adding-locale.md +353 -0
- data/docs/guides/api-backed-models.md +478 -0
- data/docs/guides/attachments.md +399 -0
- data/docs/guides/auditing.md +333 -0
- data/docs/guides/batch-actions.md +342 -0
- data/docs/guides/composite-pages.md +1290 -0
- data/docs/guides/computed-fields.md +350 -0
- data/docs/guides/conditional-rendering.md +832 -0
- data/docs/guides/custom-actions.md +238 -0
- data/docs/guides/custom-fields.md +439 -0
- data/docs/guides/custom-renderers.md +234 -0
- data/docs/guides/custom-types.md +274 -0
- data/docs/guides/dashboards.md +504 -0
- data/docs/guides/debugging/README.md +38 -0
- data/docs/guides/debugging/controllers.md +147 -0
- data/docs/guides/debugging/data.md +165 -0
- data/docs/guides/debugging/metadata.md +143 -0
- data/docs/guides/debugging/models.md +126 -0
- data/docs/guides/debugging/permissions.md +147 -0
- data/docs/guides/debugging/presenters.md +151 -0
- data/docs/guides/developer-tools.md +418 -0
- data/docs/guides/dialogs.md +392 -0
- data/docs/guides/display-types.md +1430 -0
- data/docs/guides/eager-loading.md +230 -0
- data/docs/guides/event-handlers.md +135 -0
- data/docs/guides/export.md +305 -0
- data/docs/guides/extensibility.md +761 -0
- data/docs/guides/groups.md +220 -0
- data/docs/guides/hierarchical-authorization.md +427 -0
- data/docs/guides/host-application.md +556 -0
- data/docs/guides/host-controller-integration.md +473 -0
- data/docs/guides/impersonation.md +83 -0
- data/docs/guides/import.md +165 -0
- data/docs/guides/inherited-permissions.md +459 -0
- data/docs/guides/menu.md +373 -0
- data/docs/guides/monitoring.md +254 -0
- data/docs/guides/oidc-setup.md +399 -0
- data/docs/guides/permission-source.md +205 -0
- data/docs/guides/permissions.md +364 -0
- data/docs/guides/presenters.md +2324 -0
- data/docs/guides/record-aliases.md +303 -0
- data/docs/guides/rendering-extension-points.md +280 -0
- data/docs/guides/role-source.md +288 -0
- data/docs/guides/selectbox.md +516 -0
- data/docs/guides/sequences.md +291 -0
- data/docs/guides/soft-delete.md +460 -0
- data/docs/guides/theming.md +129 -0
- data/docs/guides/tiles.md +383 -0
- data/docs/guides/tree-structures.md +297 -0
- data/docs/guides/userstamps.md +288 -0
- data/docs/guides/view-groups.md +259 -0
- data/docs/guides/view-slots.md +352 -0
- data/docs/guides/virtual-columns.md +810 -0
- data/docs/guides/workflow.md +692 -0
- data/docs/reference/api-backed-models.md +404 -0
- data/docs/reference/api-tokens.md +128 -0
- data/docs/reference/auditing.md +277 -0
- data/docs/reference/boot_lifecycle.md +188 -0
- data/docs/reference/cascading_selects.md +189 -0
- data/docs/reference/condition-operators.md +445 -0
- data/docs/reference/custom-fields.md +483 -0
- data/docs/reference/dialogs.md +286 -0
- data/docs/reference/doctor.md +168 -0
- data/docs/reference/dynamic-references.md +95 -0
- data/docs/reference/eager-loading.md +192 -0
- data/docs/reference/engine-configuration.md +989 -0
- data/docs/reference/export.md +309 -0
- data/docs/reference/forms.md +68 -0
- data/docs/reference/groups.md +176 -0
- data/docs/reference/host-controller-integration.md +342 -0
- data/docs/reference/i18n.md +497 -0
- data/docs/reference/i18n_check.md +351 -0
- data/docs/reference/import.md +260 -0
- data/docs/reference/invariant_check.md +216 -0
- data/docs/reference/menu.md +985 -0
- data/docs/reference/model-dsl.md +1157 -0
- data/docs/reference/models.md +2972 -0
- data/docs/reference/monitoring.md +222 -0
- data/docs/reference/oidc-bearer.md +269 -0
- data/docs/reference/oidc.md +407 -0
- data/docs/reference/page_filters.md +328 -0
- data/docs/reference/pages.md +1375 -0
- data/docs/reference/permission-source.md +185 -0
- data/docs/reference/permissions.md +715 -0
- data/docs/reference/presenter-dsl.md +1719 -0
- data/docs/reference/presenters.md +3627 -0
- data/docs/reference/role-source.md +227 -0
- data/docs/reference/theme-variables.md +139 -0
- data/docs/reference/tree-structures.md +374 -0
- data/docs/reference/types.md +470 -0
- data/docs/reference/view-groups.md +347 -0
- data/docs/reference/view-slots.md +228 -0
- data/docs/reference/virtual_forms.md +196 -0
- data/docs/reference/workflow-approvals.md +387 -0
- data/docs/reference/workflow.md +651 -0
- data/examples/crm/Gemfile +9 -0
- data/examples/crm/Gemfile.lock +417 -0
- data/examples/crm/Rakefile +2 -0
- data/examples/crm/app/actions/activity/complete.rb +20 -0
- data/examples/crm/app/actions/deal/close_won.rb +20 -0
- data/examples/crm/app/assets/config/manifest.js +3 -0
- data/examples/crm/app/controllers/application_controller.rb +9 -0
- data/examples/crm/app/event_handlers/deal/on_stage_change.rb +17 -0
- data/examples/crm/app/lcp_services/computed/weighted_deal_value.rb +13 -0
- data/examples/crm/app/lcp_services/data_providers/active_contacts_count.rb +14 -0
- data/examples/crm/app/lcp_services/data_providers/open_deals_count.rb +14 -0
- data/examples/crm/app/lcp_services/data_providers/pending_activities_count.rb +15 -0
- data/examples/crm/app/lcp_services/data_providers/pipeline_value.rb +27 -0
- data/examples/crm/app/lcp_services/data_providers/won_deals_count.rb +14 -0
- data/examples/crm/app/lcp_services/defaults/thirty_days_out.rb +11 -0
- data/examples/crm/app/lcp_services/transforms/titlecase.rb +11 -0
- data/examples/crm/app/lcp_services/validators/deal_credit_limit.rb +19 -0
- data/examples/crm/app/lcp_services/validators/deal_documents_required.rb +17 -0
- data/examples/crm/app/renderers/conditional_badge.rb +39 -0
- data/examples/crm/bin/rails +4 -0
- data/examples/crm/bin/rake +4 -0
- data/examples/crm/config/application.rb +31 -0
- data/examples/crm/config/boot.rb +2 -0
- data/examples/crm/config/database.yml +12 -0
- data/examples/crm/config/environment.rb +2 -0
- data/examples/crm/config/initializers/lcp_ruby.rb +45 -0
- data/examples/crm/config/lcp_ruby/menu.yml +53 -0
- data/examples/crm/config/lcp_ruby/models/activity.rb +43 -0
- data/examples/crm/config/lcp_ruby/models/city.rb +19 -0
- data/examples/crm/config/lcp_ruby/models/company.rb +65 -0
- data/examples/crm/config/lcp_ruby/models/contact.rb +62 -0
- data/examples/crm/config/lcp_ruby/models/country.rb +22 -0
- data/examples/crm/config/lcp_ruby/models/custom_field_definition.rb +60 -0
- data/examples/crm/config/lcp_ruby/models/deal.rb +85 -0
- data/examples/crm/config/lcp_ruby/models/deal_category.rb +15 -0
- data/examples/crm/config/lcp_ruby/models/gapfree_sequence.rb +17 -0
- data/examples/crm/config/lcp_ruby/models/region.rb +15 -0
- data/examples/crm/config/lcp_ruby/models/saved_filter.rb +50 -0
- data/examples/crm/config/lcp_ruby/pages/activity_quick_log.yml +19 -0
- data/examples/crm/config/lcp_ruby/pages/company_detail.yml +127 -0
- data/examples/crm/config/lcp_ruby/pages/deals_overview.yml +65 -0
- data/examples/crm/config/lcp_ruby/permissions/activity.yml +40 -0
- data/examples/crm/config/lcp_ruby/permissions/custom_field_definition.yml +16 -0
- data/examples/crm/config/lcp_ruby/permissions/deal.yml +54 -0
- data/examples/crm/config/lcp_ruby/permissions/default.yml +16 -0
- data/examples/crm/config/lcp_ruby/permissions/gapfree_sequence.yml +6 -0
- data/examples/crm/config/lcp_ruby/permissions/saved_filter.yml +27 -0
- data/examples/crm/config/lcp_ruby/presenters/activity.rb +140 -0
- data/examples/crm/config/lcp_ruby/presenters/activity_quick_form.rb +36 -0
- data/examples/crm/config/lcp_ruby/presenters/activity_short.rb +16 -0
- data/examples/crm/config/lcp_ruby/presenters/activity_tiles.rb +35 -0
- data/examples/crm/config/lcp_ruby/presenters/city.rb +53 -0
- data/examples/crm/config/lcp_ruby/presenters/company.rb +147 -0
- data/examples/crm/config/lcp_ruby/presenters/company_activities_zone.rb +26 -0
- data/examples/crm/config/lcp_ruby/presenters/company_archive.rb +37 -0
- data/examples/crm/config/lcp_ruby/presenters/company_contacts_zone.rb +22 -0
- data/examples/crm/config/lcp_ruby/presenters/company_deals_zone.rb +36 -0
- data/examples/crm/config/lcp_ruby/presenters/company_short.rb +21 -0
- data/examples/crm/config/lcp_ruby/presenters/company_show_zone.rb +38 -0
- data/examples/crm/config/lcp_ruby/presenters/company_sidebar.rb +15 -0
- data/examples/crm/config/lcp_ruby/presenters/company_tiles.rb +35 -0
- data/examples/crm/config/lcp_ruby/presenters/contact.rb +120 -0
- data/examples/crm/config/lcp_ruby/presenters/contact_quick_form.rb +17 -0
- data/examples/crm/config/lcp_ruby/presenters/contact_short.rb +20 -0
- data/examples/crm/config/lcp_ruby/presenters/contact_tiles.rb +34 -0
- data/examples/crm/config/lcp_ruby/presenters/country.rb +44 -0
- data/examples/crm/config/lcp_ruby/presenters/custom_fields.rb +124 -0
- data/examples/crm/config/lcp_ruby/presenters/deal.rb +181 -0
- data/examples/crm/config/lcp_ruby/presenters/deal_category.rb +46 -0
- data/examples/crm/config/lcp_ruby/presenters/deal_overview.rb +6 -0
- data/examples/crm/config/lcp_ruby/presenters/deal_pipeline.rb +18 -0
- data/examples/crm/config/lcp_ruby/presenters/deal_short.rb +25 -0
- data/examples/crm/config/lcp_ruby/presenters/deal_tiles.rb +48 -0
- data/examples/crm/config/lcp_ruby/presenters/region.rb +42 -0
- data/examples/crm/config/lcp_ruby/presenters/save_filter_dialog.rb +17 -0
- data/examples/crm/config/lcp_ruby/presenters/saved_filters.rb +93 -0
- data/examples/crm/config/lcp_ruby/views/activities.yml +16 -0
- data/examples/crm/config/lcp_ruby/views/cities.yml +10 -0
- data/examples/crm/config/lcp_ruby/views/companies.yml +14 -0
- data/examples/crm/config/lcp_ruby/views/contacts.yml +16 -0
- data/examples/crm/config/lcp_ruby/views/countries.yml +8 -0
- data/examples/crm/config/lcp_ruby/views/custom_fields.rb +9 -0
- data/examples/crm/config/lcp_ruby/views/deal_categories.yml +8 -0
- data/examples/crm/config/lcp_ruby/views/deals.yml +19 -0
- data/examples/crm/config/lcp_ruby/views/pipeline.yml +10 -0
- data/examples/crm/config/lcp_ruby/views/regions.yml +10 -0
- data/examples/crm/config/lcp_ruby/views/saved_filters.yml +7 -0
- data/examples/crm/config/locales/cs.yml +338 -0
- data/examples/crm/config/locales/en.yml +353 -0
- data/examples/crm/config/routes.rb +4 -0
- data/examples/crm/config/storage.yml +3 -0
- data/examples/crm/config.ru +2 -0
- data/examples/crm/db/migrate/20260219104942_create_active_storage_tables.active_storage.rb +57 -0
- data/examples/crm/db/schema.rb +245 -0
- data/examples/crm/db/seeds.rb +1111 -0
- data/examples/crm/erd.md +163 -0
- data/examples/hr/Gemfile +9 -0
- data/examples/hr/Gemfile.lock +419 -0
- data/examples/hr/Rakefile +6 -0
- data/examples/hr/app/actions/asset/assign_asset.rb +20 -0
- data/examples/hr/app/actions/asset/return_asset.rb +20 -0
- data/examples/hr/app/actions/candidate/advance.rb +32 -0
- data/examples/hr/app/actions/candidate/hire.rb +20 -0
- data/examples/hr/app/actions/candidate/reject_candidate.rb +20 -0
- data/examples/hr/app/actions/expense_claim/approve.rb +21 -0
- data/examples/hr/app/actions/expense_claim/reject.rb +21 -0
- data/examples/hr/app/actions/expense_claim/submit.rb +20 -0
- data/examples/hr/app/actions/interview/complete_interview.rb +20 -0
- data/examples/hr/app/actions/leave_request/approve.rb +21 -0
- data/examples/hr/app/actions/leave_request/cancel.rb +20 -0
- data/examples/hr/app/actions/leave_request/reject.rb +21 -0
- data/examples/hr/app/assets/config/manifest.js +1 -0
- data/examples/hr/app/assets/stylesheets/application.css +10 -0
- data/examples/hr/app/condition_services/is_own_org_unit.rb +14 -0
- data/examples/hr/app/condition_services/is_own_record.rb +20 -0
- data/examples/hr/app/controllers/application_controller.rb +15 -0
- data/examples/hr/app/event_handlers/asset_assignment/on_create.rb +24 -0
- data/examples/hr/app/event_handlers/candidate/on_status_change.rb +18 -0
- data/examples/hr/app/event_handlers/leave_request/on_status_change.rb +45 -0
- data/examples/hr/app/helpers/application_helper.rb +2 -0
- data/examples/hr/app/javascript/application.js +1 -0
- data/examples/hr/app/jobs/application_job.rb +7 -0
- data/examples/hr/app/lcp_services/computed/employee_tenure.rb +28 -0
- data/examples/hr/app/lcp_services/computed/leave_remaining.rb +13 -0
- data/examples/hr/app/lcp_services/data_providers/headcount_text.rb +14 -0
- data/examples/hr/app/lcp_services/data_providers/open_positions_count.rb +14 -0
- data/examples/hr/app/lcp_services/data_providers/pending_expenses_count.rb +14 -0
- data/examples/hr/app/lcp_services/data_providers/pending_leaves_count.rb +14 -0
- data/examples/hr/app/lcp_services/defaults/current_year.rb +11 -0
- data/examples/hr/app/lcp_services/transforms/titlecase.rb +11 -0
- data/examples/hr/app/lcp_services/validators/expense_receipt_required.rb +17 -0
- data/examples/hr/app/lcp_services/validators/leave_balance_check.rb +37 -0
- data/examples/hr/app/models/application_record.rb +3 -0
- data/examples/hr/app/views/layouts/application.html.erb +29 -0
- data/examples/hr/app/views/pwa/manifest.json.erb +22 -0
- data/examples/hr/app/views/pwa/service-worker.js +26 -0
- data/examples/hr/bin/brakeman +7 -0
- data/examples/hr/bin/bundler-audit +6 -0
- data/examples/hr/bin/ci +6 -0
- data/examples/hr/bin/dev +2 -0
- data/examples/hr/bin/docker-entrypoint +8 -0
- data/examples/hr/bin/importmap +4 -0
- data/examples/hr/bin/jobs +6 -0
- data/examples/hr/bin/kamal +27 -0
- data/examples/hr/bin/rails +4 -0
- data/examples/hr/bin/rake +4 -0
- data/examples/hr/bin/rubocop +8 -0
- data/examples/hr/bin/setup +35 -0
- data/examples/hr/bin/thrust +5 -0
- data/examples/hr/config/application.rb +31 -0
- data/examples/hr/config/boot.rb +2 -0
- data/examples/hr/config/bundler-audit.yml +5 -0
- data/examples/hr/config/cache.yml +16 -0
- data/examples/hr/config/ci.rb +20 -0
- data/examples/hr/config/credentials.yml.enc +1 -0
- data/examples/hr/config/database.yml +36 -0
- data/examples/hr/config/deploy.yml +119 -0
- data/examples/hr/config/environment.rb +5 -0
- data/examples/hr/config/environments/development.rb +66 -0
- data/examples/hr/config/environments/production.rb +74 -0
- data/examples/hr/config/environments/test.rb +45 -0
- data/examples/hr/config/importmap.rb +3 -0
- data/examples/hr/config/initializers/assets.rb +7 -0
- data/examples/hr/config/initializers/content_security_policy.rb +29 -0
- data/examples/hr/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/hr/config/initializers/inflections.rb +16 -0
- data/examples/hr/config/initializers/lcp_ruby.rb +18 -0
- data/examples/hr/config/lcp_ruby/menu.yml +86 -0
- data/examples/hr/config/lcp_ruby/models/announcement.rb +33 -0
- data/examples/hr/config/lcp_ruby/models/asset.rb +73 -0
- data/examples/hr/config/lcp_ruby/models/asset_assignment.rb +39 -0
- data/examples/hr/config/lcp_ruby/models/audit_log.yml +57 -0
- data/examples/hr/config/lcp_ruby/models/candidate.rb +68 -0
- data/examples/hr/config/lcp_ruby/models/custom_field_definition.rb +60 -0
- data/examples/hr/config/lcp_ruby/models/dashboard.rb +17 -0
- data/examples/hr/config/lcp_ruby/models/document.rb +38 -0
- data/examples/hr/config/lcp_ruby/models/employee.rb +128 -0
- data/examples/hr/config/lcp_ruby/models/employee_skill.rb +29 -0
- data/examples/hr/config/lcp_ruby/models/expense_claim.rb +70 -0
- data/examples/hr/config/lcp_ruby/models/goal.rb +48 -0
- data/examples/hr/config/lcp_ruby/models/group.rb +37 -0
- data/examples/hr/config/lcp_ruby/models/group_membership.rb +26 -0
- data/examples/hr/config/lcp_ruby/models/interview.rb +54 -0
- data/examples/hr/config/lcp_ruby/models/job_posting.rb +67 -0
- data/examples/hr/config/lcp_ruby/models/leave_balance.rb +31 -0
- data/examples/hr/config/lcp_ruby/models/leave_request.rb +52 -0
- data/examples/hr/config/lcp_ruby/models/leave_type.rb +35 -0
- data/examples/hr/config/lcp_ruby/models/organization_unit.rb +42 -0
- data/examples/hr/config/lcp_ruby/models/performance_review.rb +59 -0
- data/examples/hr/config/lcp_ruby/models/position.rb +43 -0
- data/examples/hr/config/lcp_ruby/models/skill.rb +27 -0
- data/examples/hr/config/lcp_ruby/models/training_course.rb +53 -0
- data/examples/hr/config/lcp_ruby/models/training_enrollment.rb +35 -0
- data/examples/hr/config/lcp_ruby/pages/dashboard.yml +101 -0
- data/examples/hr/config/lcp_ruby/permissions/announcement.yml +25 -0
- data/examples/hr/config/lcp_ruby/permissions/asset.yml +35 -0
- data/examples/hr/config/lcp_ruby/permissions/audit_log.yml +28 -0
- data/examples/hr/config/lcp_ruby/permissions/candidate.yml +25 -0
- data/examples/hr/config/lcp_ruby/permissions/custom_field_definition.yml +28 -0
- data/examples/hr/config/lcp_ruby/permissions/dashboard.yml +25 -0
- data/examples/hr/config/lcp_ruby/permissions/default.yml +25 -0
- data/examples/hr/config/lcp_ruby/permissions/document.yml +37 -0
- data/examples/hr/config/lcp_ruby/permissions/employee.yml +55 -0
- data/examples/hr/config/lcp_ruby/permissions/expense_claim.yml +45 -0
- data/examples/hr/config/lcp_ruby/permissions/group.yml +27 -0
- data/examples/hr/config/lcp_ruby/permissions/job_posting.yml +34 -0
- data/examples/hr/config/lcp_ruby/permissions/leave_request.yml +45 -0
- data/examples/hr/config/lcp_ruby/permissions/performance_review.yml +42 -0
- data/examples/hr/config/lcp_ruby/presenters/announcement.rb +47 -0
- data/examples/hr/config/lcp_ruby/presenters/asset.rb +69 -0
- data/examples/hr/config/lcp_ruby/presenters/asset_assignment.rb +47 -0
- data/examples/hr/config/lcp_ruby/presenters/audit_logs.yml +43 -0
- data/examples/hr/config/lcp_ruby/presenters/candidate.rb +71 -0
- data/examples/hr/config/lcp_ruby/presenters/custom_fields.rb +124 -0
- data/examples/hr/config/lcp_ruby/presenters/dashboard.rb +37 -0
- data/examples/hr/config/lcp_ruby/presenters/document.rb +46 -0
- data/examples/hr/config/lcp_ruby/presenters/employee.rb +167 -0
- data/examples/hr/config/lcp_ruby/presenters/employee_archive.rb +38 -0
- data/examples/hr/config/lcp_ruby/presenters/employee_directory.rb +27 -0
- data/examples/hr/config/lcp_ruby/presenters/employee_skill.rb +57 -0
- data/examples/hr/config/lcp_ruby/presenters/expense_claim.rb +76 -0
- data/examples/hr/config/lcp_ruby/presenters/goal.rb +60 -0
- data/examples/hr/config/lcp_ruby/presenters/group.rb +48 -0
- data/examples/hr/config/lcp_ruby/presenters/interview.rb +59 -0
- data/examples/hr/config/lcp_ruby/presenters/job_posting.rb +73 -0
- data/examples/hr/config/lcp_ruby/presenters/leave_balance.rb +50 -0
- data/examples/hr/config/lcp_ruby/presenters/leave_request.rb +89 -0
- data/examples/hr/config/lcp_ruby/presenters/leave_type.rb +52 -0
- data/examples/hr/config/lcp_ruby/presenters/organization_unit.rb +56 -0
- data/examples/hr/config/lcp_ruby/presenters/performance_review.rb +87 -0
- data/examples/hr/config/lcp_ruby/presenters/position.rb +54 -0
- data/examples/hr/config/lcp_ruby/presenters/skill.rb +45 -0
- data/examples/hr/config/lcp_ruby/presenters/training_course.rb +61 -0
- data/examples/hr/config/lcp_ruby/presenters/training_enrollment.rb +49 -0
- data/examples/hr/config/lcp_ruby/views/announcements.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/asset_assignments.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/assets.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/audit_logs.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/candidates.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/custom_fields.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/dashboard.yml +6 -0
- data/examples/hr/config/lcp_ruby/views/documents.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/employee_skills.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/employees.yml +14 -0
- data/examples/hr/config/lcp_ruby/views/expense_claims.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/goals.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/groups.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/interviews.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/job_postings.yml +6 -0
- data/examples/hr/config/lcp_ruby/views/leave_balances.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/leave_requests.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/leave_types.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/organization_units.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/performance_reviews.yml +10 -0
- data/examples/hr/config/lcp_ruby/views/positions.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/skills.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/training_courses.yml +8 -0
- data/examples/hr/config/lcp_ruby/views/training_enrollments.yml +10 -0
- data/examples/hr/config/locales/cs.yml +496 -0
- data/examples/hr/config/locales/en.yml +740 -0
- data/examples/hr/config/locales/sk.yml +496 -0
- data/examples/hr/config/puma.rb +42 -0
- data/examples/hr/config/queue.yml +18 -0
- data/examples/hr/config/recurring.yml +15 -0
- data/examples/hr/config/routes.rb +4 -0
- data/examples/hr/config/storage.yml +27 -0
- data/examples/hr/config.ru +6 -0
- data/examples/hr/db/cache_schema.rb +12 -0
- data/examples/hr/db/migrate/20260303202825_create_active_storage_tables.active_storage.rb +57 -0
- data/examples/hr/db/queue_schema.rb +129 -0
- data/examples/hr/db/schema.rb +588 -0
- data/examples/hr/db/seeds.rb +932 -0
- data/examples/hr/erd.md +396 -0
- data/examples/hr/public/400.html +135 -0
- data/examples/hr/public/404.html +135 -0
- data/examples/hr/public/406-unsupported-browser.html +135 -0
- data/examples/hr/public/422.html +135 -0
- data/examples/hr/public/500.html +135 -0
- data/examples/hr/public/icon.svg +3 -0
- data/examples/hr/public/robots.txt +1 -0
- data/examples/showcase/Gemfile +15 -0
- data/examples/showcase/Gemfile.lock +425 -0
- data/examples/showcase/Rakefile +2 -0
- data/examples/showcase/app/actions/showcase_batch_task/assign_batch.rb +20 -0
- data/examples/showcase/app/actions/showcase_batch_task/close_task.rb +21 -0
- data/examples/showcase/app/actions/showcase_condition/approve.rb +20 -0
- data/examples/showcase/app/actions/showcase_permission/lock.rb +20 -0
- data/examples/showcase/app/assets/config/manifest.js +3 -0
- data/examples/showcase/app/assets/stylesheets/application.css +27 -0
- data/examples/showcase/app/condition_services/budget_threshold.rb +16 -0
- data/examples/showcase/app/condition_services/overdue_check.rb +11 -0
- data/examples/showcase/app/controllers/application_controller.rb +2 -0
- data/examples/showcase/app/controllers/docs_controller.rb +48 -0
- data/examples/showcase/app/controllers/host_inventory_items_managed_controller.rb +90 -0
- data/examples/showcase/app/controllers/host_inventory_items_report_controller.rb +39 -0
- data/examples/showcase/app/controllers/host_inventory_items_wizard_controller.rb +50 -0
- data/examples/showcase/app/data_providers/showcase_amount_provider.rb +52 -0
- data/examples/showcase/app/data_providers/weather_station_provider.rb +140 -0
- data/examples/showcase/app/event_handlers/showcase_model/on_status_change.rb +17 -0
- data/examples/showcase/app/event_handlers/showcase_permission/on_status_change.rb +17 -0
- data/examples/showcase/app/event_handlers/showcase_workflow/log_review_exit.rb +16 -0
- data/examples/showcase/app/event_handlers/showcase_workflow/notify_reviewers.rb +15 -0
- data/examples/showcase/app/event_handlers/showcase_workflow/request_submitted.rb +15 -0
- data/examples/showcase/app/lcp_metrics/showcase_metrics.rb +31 -0
- data/examples/showcase/app/lcp_services/computed/showcase_score.rb +18 -0
- data/examples/showcase/app/lcp_services/computed/showcase_total.rb +13 -0
- data/examples/showcase/app/lcp_services/defaults/one_week_from_now.rb +11 -0
- data/examples/showcase/app/lcp_services/menu_items/recent_announcements.rb +36 -0
- data/examples/showcase/app/lcp_services/virtual_columns/project_health.rb +26 -0
- data/examples/showcase/app/model_extensions/lcp_error_log_extension.rb +12 -0
- data/examples/showcase/app/models/host_inventory_item.rb +20 -0
- data/examples/showcase/app/models/platform_profile.rb +27 -0
- data/examples/showcase/app/models/platform_user.rb +5 -0
- data/examples/showcase/app/views/docs/show.html.erb +19 -0
- data/examples/showcase/app/views/host_inventory_items_report/index.html.erb +61 -0
- data/examples/showcase/app/views/host_inventory_items_report/show.html.erb +39 -0
- data/examples/showcase/app/views/layouts/docs.html.erb +46 -0
- data/examples/showcase/app/views/showcase/menu_renderers/_status_pill.html.erb +17 -0
- data/examples/showcase/app/views/showcase_custom/_activity_timeline.html.erb +37 -0
- data/examples/showcase/app/views/showcase_custom/_card_index.html.erb +74 -0
- data/examples/showcase/app/views/showcase_custom/_detail_show.html.erb +115 -0
- data/examples/showcase/app/views/showcase_custom/_location_map.html.erb +29 -0
- data/examples/showcase/app/views/showcase_custom/_location_view.html.erb +34 -0
- data/examples/showcase/app/views/showcase_custom/_quick_stats.html.erb +50 -0
- data/examples/showcase/app/views/showcase_custom/_stats_sidebar.html.erb +42 -0
- data/examples/showcase/app/views/showcase_custom/_tags_editor.html.erb +48 -0
- data/examples/showcase/bin/rails +4 -0
- data/examples/showcase/bin/rake +4 -0
- data/examples/showcase/config/application.rb +37 -0
- data/examples/showcase/config/boot.rb +2 -0
- data/examples/showcase/config/database.yml +12 -0
- data/examples/showcase/config/environment.rb +2 -0
- data/examples/showcase/config/initializers/devise.rb +5 -0
- data/examples/showcase/config/initializers/lcp_ruby.rb +175 -0
- data/examples/showcase/config/lcp_ruby/auth.yml +60 -0
- data/examples/showcase/config/lcp_ruby/jobs/data_import.yml +7 -0
- data/examples/showcase/config/lcp_ruby/jobs/showcase_cleanup.yml +11 -0
- data/examples/showcase/config/lcp_ruby/jobs/showcase_event_triggered.yml +11 -0
- data/examples/showcase/config/lcp_ruby/jobs/showcase_multi_step.yml +9 -0
- data/examples/showcase/config/lcp_ruby/jobs/showcase_webhook.yml +13 -0
- data/examples/showcase/config/lcp_ruby/menu.yml +249 -0
- data/examples/showcase/config/lcp_ruby/models/_base_document.rb +26 -0
- data/examples/showcase/config/lcp_ruby/models/_categorized_document.rb +19 -0
- data/examples/showcase/config/lcp_ruby/models/_contactable.rb +20 -0
- data/examples/showcase/config/lcp_ruby/models/api_token.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/article.rb +49 -0
- data/examples/showcase/config/lcp_ruby/models/article_tag.rb +10 -0
- data/examples/showcase/config/lcp_ruby/models/author.rb +16 -0
- data/examples/showcase/config/lcp_ruby/models/batch_operation.yml +79 -0
- data/examples/showcase/config/lcp_ruby/models/batch_operation_item.yml +45 -0
- data/examples/showcase/config/lcp_ruby/models/category.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/comment.rb +21 -0
- data/examples/showcase/config/lcp_ruby/models/custom_field_definition.rb +59 -0
- data/examples/showcase/config/lcp_ruby/models/department.rb +32 -0
- data/examples/showcase/config/lcp_ruby/models/employee.rb +52 -0
- data/examples/showcase/config/lcp_ruby/models/employee_emergency_contact.rb +24 -0
- data/examples/showcase/config/lcp_ruby/models/employee_profile.rb +18 -0
- data/examples/showcase/config/lcp_ruby/models/employee_skill.rb +10 -0
- data/examples/showcase/config/lcp_ruby/models/export_log.yml +27 -0
- data/examples/showcase/config/lcp_ruby/models/export_profile.yml +37 -0
- data/examples/showcase/config/lcp_ruby/models/feature.rb +76 -0
- data/examples/showcase/config/lcp_ruby/models/gapfree_sequence.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/group.rb +26 -0
- data/examples/showcase/config/lcp_ruby/models/group_membership.rb +21 -0
- data/examples/showcase/config/lcp_ruby/models/group_role_mapping.rb +14 -0
- data/examples/showcase/config/lcp_ruby/models/host_inventory_item.yml +111 -0
- data/examples/showcase/config/lcp_ruby/models/import_profile.rb +30 -0
- data/examples/showcase/config/lcp_ruby/models/import_row.rb +32 -0
- data/examples/showcase/config/lcp_ruby/models/ingredient_def.rb +11 -0
- data/examples/showcase/config/lcp_ruby/models/lcp_error_log.yml +61 -0
- data/examples/showcase/config/lcp_ruby/models/page_config.rb +19 -0
- data/examples/showcase/config/lcp_ruby/models/permission_config.rb +21 -0
- data/examples/showcase/config/lcp_ruby/models/pipeline.rb +15 -0
- data/examples/showcase/config/lcp_ruby/models/pipeline_stage.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/platform_profile.rb +104 -0
- data/examples/showcase/config/lcp_ruby/models/profile_setting.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/profile_tag.rb +19 -0
- data/examples/showcase/config/lcp_ruby/models/project.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/role.rb +24 -0
- data/examples/showcase/config/lcp_ruby/models/saved_filter.rb +50 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_aggregate.rb +93 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_aggregate_company.rb +14 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_aggregate_item.rb +25 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_announcement.rb +20 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_array.rb +45 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_attachment.rb +49 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_audit_log.yml +44 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_audited_record.rb +20 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_batch_task.rb +27 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_business_unit.rb +26 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_condition.rb +24 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_condition_category.rb +16 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_condition_task.rb +16 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_condition_threshold.rb +13 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_contact.rb +22 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_custom_render.rb +38 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_delete_reason.rb +11 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_division.rb +31 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_extensibility.rb +25 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_field.rb +53 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_form.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_form_action.rb +38 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_grade.rb +18 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_hr_employee.rb +90 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_item_class.rb +31 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_job_execution.rb +49 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_memo.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_model.rb +74 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_organization.rb +31 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_permission.rb +30 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_person.rb +23 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_positioning.rb +19 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_quick_note.rb +13 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_recipe.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_report.rb +32 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_school_class.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_search.rb +84 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_sequence.rb +46 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_soft_delete.rb +30 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_soft_delete_item.rb +21 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_student.rb +17 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_type_default.rb +49 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_userstamps.rb +26 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_virtual_field.rb +68 -0
- data/examples/showcase/config/lcp_ruby/models/showcase_workflow.rb +54 -0
- data/examples/showcase/config/lcp_ruby/models/skill.rb +22 -0
- data/examples/showcase/config/lcp_ruby/models/tag.rb +16 -0
- data/examples/showcase/config/lcp_ruby/models/user.rb +54 -0
- data/examples/showcase/config/lcp_ruby/models/weather_station.rb +20 -0
- data/examples/showcase/config/lcp_ruby/models/workflow_approval_request.yml +77 -0
- data/examples/showcase/config/lcp_ruby/models/workflow_approval_step.yml +56 -0
- data/examples/showcase/config/lcp_ruby/models/workflow_approval_task.yml +51 -0
- data/examples/showcase/config/lcp_ruby/models/workflow_audit_log.yml +74 -0
- data/examples/showcase/config/lcp_ruby/pages/article_detail.yml +32 -0
- data/examples/showcase/config/lcp_ruby/pages/author_detail.yml +27 -0
- data/examples/showcase/config/lcp_ruby/pages/category_detail.yml +24 -0
- data/examples/showcase/config/lcp_ruby/pages/chart_showcase.yml +151 -0
- data/examples/showcase/config/lcp_ruby/pages/department_detail.yml +55 -0
- data/examples/showcase/config/lcp_ruby/pages/department_explorer.yml +60 -0
- data/examples/showcase/config/lcp_ruby/pages/employee_overview.yml +106 -0
- data/examples/showcase/config/lcp_ruby/pages/employee_transfer_dialog.yml +24 -0
- data/examples/showcase/config/lcp_ruby/pages/hr_turnover_dashboard.yml +288 -0
- data/examples/showcase/config/lcp_ruby/pages/main_dashboard.yml +142 -0
- data/examples/showcase/config/lcp_ruby/pages/monitoring_dashboard.yml +58 -0
- data/examples/showcase/config/lcp_ruby/pages/pipeline_detail.yml +16 -0
- data/examples/showcase/config/lcp_ruby/pages/showcase_custom_zones.yml +39 -0
- data/examples/showcase/config/lcp_ruby/pages/showcase_form_action_dialog.yml +11 -0
- data/examples/showcase/config/lcp_ruby/pages/workflow_request_detail.yml +34 -0
- data/examples/showcase/config/lcp_ruby/permissions/api_token.yml +26 -0
- data/examples/showcase/config/lcp_ruby/permissions/batch_operation.yml +36 -0
- data/examples/showcase/config/lcp_ruby/permissions/custom_field_definition.yml +15 -0
- data/examples/showcase/config/lcp_ruby/permissions/default.yml +28 -0
- data/examples/showcase/config/lcp_ruby/permissions/export_log.yml +23 -0
- data/examples/showcase/config/lcp_ruby/permissions/export_profile.yml +25 -0
- data/examples/showcase/config/lcp_ruby/permissions/gapfree_sequence.yml +6 -0
- data/examples/showcase/config/lcp_ruby/permissions/group.yml +20 -0
- data/examples/showcase/config/lcp_ruby/permissions/group_membership.yml +20 -0
- data/examples/showcase/config/lcp_ruby/permissions/group_role_mapping.yml +20 -0
- data/examples/showcase/config/lcp_ruby/permissions/host_inventory_item.yml +34 -0
- data/examples/showcase/config/lcp_ruby/permissions/import_profile.yml +29 -0
- data/examples/showcase/config/lcp_ruby/permissions/import_row.yml +17 -0
- data/examples/showcase/config/lcp_ruby/permissions/lcp_error_log.yml +8 -0
- data/examples/showcase/config/lcp_ruby/permissions/page_config.yml +15 -0
- data/examples/showcase/config/lcp_ruby/permissions/permission_config.yml +15 -0
- data/examples/showcase/config/lcp_ruby/permissions/platform_profile.yml +23 -0
- data/examples/showcase/config/lcp_ruby/permissions/profile_setting.yml +23 -0
- data/examples/showcase/config/lcp_ruby/permissions/profile_tag.yml +23 -0
- data/examples/showcase/config/lcp_ruby/permissions/role.yml +20 -0
- data/examples/showcase/config/lcp_ruby/permissions/saved_filter.yml +39 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_announcement.yml +22 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_audit_log.yml +15 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_audited_record.yml +15 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_batch_task.yml +31 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_condition.yml +54 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_contact.yml +21 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_custom_render.yml +21 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_delete_reason.yml +12 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_form_action.yml +26 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_grade.yml +33 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_job_execution.yml +23 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_permission.yml +47 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_quick_note.yml +12 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_school_class.yml +37 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_student.yml +43 -0
- data/examples/showcase/config/lcp_ruby/permissions/showcase_workflow.yml +34 -0
- data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_request.yml +18 -0
- data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_step.yml +18 -0
- data/examples/showcase/config/lcp_ruby/permissions/workflow_approval_task.yml +18 -0
- data/examples/showcase/config/lcp_ruby/permissions/workflow_audit_log.yml +19 -0
- data/examples/showcase/config/lcp_ruby/presenters/announcements.rb +59 -0
- data/examples/showcase/config/lcp_ruby/presenters/approval_requests.rb +69 -0
- data/examples/showcase/config/lcp_ruby/presenters/approval_steps.rb +59 -0
- data/examples/showcase/config/lcp_ruby/presenters/approval_tasks.rb +65 -0
- data/examples/showcase/config/lcp_ruby/presenters/article_comments_zone.rb +12 -0
- data/examples/showcase/config/lcp_ruby/presenters/article_related_zone.rb +15 -0
- data/examples/showcase/config/lcp_ruby/presenters/articles.rb +149 -0
- data/examples/showcase/config/lcp_ruby/presenters/articles_tiles.rb +30 -0
- data/examples/showcase/config/lcp_ruby/presenters/author_articles_zone.rb +12 -0
- data/examples/showcase/config/lcp_ruby/presenters/author_show_zone.rb +12 -0
- data/examples/showcase/config/lcp_ruby/presenters/authors.rb +39 -0
- data/examples/showcase/config/lcp_ruby/presenters/batch_operation_items.yml +45 -0
- data/examples/showcase/config/lcp_ruby/presenters/batch_operations.yml +66 -0
- data/examples/showcase/config/lcp_ruby/presenters/categories.rb +50 -0
- data/examples/showcase/config/lcp_ruby/presenters/category_articles_zone.rb +20 -0
- data/examples/showcase/config/lcp_ruby/presenters/category_children_zone.rb +10 -0
- data/examples/showcase/config/lcp_ruby/presenters/comment_quick_add_dialog.rb +15 -0
- data/examples/showcase/config/lcp_ruby/presenters/custom_fields.rb +124 -0
- data/examples/showcase/config/lcp_ruby/presenters/dashboard_employees.rb +16 -0
- data/examples/showcase/config/lcp_ruby/presenters/delete_reason_dialog.rb +13 -0
- data/examples/showcase/config/lcp_ruby/presenters/departments.rb +55 -0
- data/examples/showcase/config/lcp_ruby/presenters/dept_add_employee_dialog.rb +19 -0
- data/examples/showcase/config/lcp_ruby/presenters/dept_children_zone.rb +10 -0
- data/examples/showcase/config/lcp_ruby/presenters/dept_detail_zone.rb +14 -0
- data/examples/showcase/config/lcp_ruby/presenters/dept_employees_zone.rb +16 -0
- data/examples/showcase/config/lcp_ruby/presenters/dept_list_selection_zone.rb +19 -0
- data/examples/showcase/config/lcp_ruby/presenters/employee_overview_index_zone.rb +36 -0
- data/examples/showcase/config/lcp_ruby/presenters/employee_quick_add_dialog.rb +14 -0
- data/examples/showcase/config/lcp_ruby/presenters/employee_show_zone.rb +20 -0
- data/examples/showcase/config/lcp_ruby/presenters/employee_transfer_form_zone.rb +19 -0
- data/examples/showcase/config/lcp_ruby/presenters/employees.rb +165 -0
- data/examples/showcase/config/lcp_ruby/presenters/employees_tiles.rb +33 -0
- data/examples/showcase/config/lcp_ruby/presenters/export_logs.yml +58 -0
- data/examples/showcase/config/lcp_ruby/presenters/export_profiles.yml +70 -0
- data/examples/showcase/config/lcp_ruby/presenters/extensibility_quick_edit_dialog.rb +13 -0
- data/examples/showcase/config/lcp_ruby/presenters/feature_kanban.rb +30 -0
- data/examples/showcase/config/lcp_ruby/presenters/features_card.rb +179 -0
- data/examples/showcase/config/lcp_ruby/presenters/features_hub.rb +44 -0
- data/examples/showcase/config/lcp_ruby/presenters/features_table.rb +37 -0
- data/examples/showcase/config/lcp_ruby/presenters/features_tiles.rb +48 -0
- data/examples/showcase/config/lcp_ruby/presenters/group_memberships.rb +54 -0
- data/examples/showcase/config/lcp_ruby/presenters/group_role_mappings.rb +48 -0
- data/examples/showcase/config/lcp_ruby/presenters/groups.rb +74 -0
- data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items.rb +99 -0
- data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_managed.rb +84 -0
- data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_report.rb +40 -0
- data/examples/showcase/config/lcp_ruby/presenters/host_inventory_items_wizard.rb +76 -0
- data/examples/showcase/config/lcp_ruby/presenters/import_profiles.rb +61 -0
- data/examples/showcase/config/lcp_ruby/presenters/import_rows.rb +39 -0
- data/examples/showcase/config/lcp_ruby/presenters/lcp_error_logs.yml +46 -0
- data/examples/showcase/config/lcp_ruby/presenters/my_api_tokens.rb +35 -0
- data/examples/showcase/config/lcp_ruby/presenters/my_api_tokens_create_dialog.rb +27 -0
- data/examples/showcase/config/lcp_ruby/presenters/my_employee_profile.rb +24 -0
- data/examples/showcase/config/lcp_ruby/presenters/my_settings.rb +50 -0
- data/examples/showcase/config/lcp_ruby/presenters/page_configs.rb +56 -0
- data/examples/showcase/config/lcp_ruby/presenters/permission_configs.rb +58 -0
- data/examples/showcase/config/lcp_ruby/presenters/pipeline_edit_zone.rb +11 -0
- data/examples/showcase/config/lcp_ruby/presenters/pipeline_stages.rb +51 -0
- data/examples/showcase/config/lcp_ruby/presenters/pipeline_stages_zone.rb +11 -0
- data/examples/showcase/config/lcp_ruby/presenters/pipelines.rb +40 -0
- data/examples/showcase/config/lcp_ruby/presenters/platform_profiles.rb +90 -0
- data/examples/showcase/config/lcp_ruby/presenters/projects.rb +58 -0
- data/examples/showcase/config/lcp_ruby/presenters/quick_note_dialog.rb +14 -0
- data/examples/showcase/config/lcp_ruby/presenters/roles.rb +60 -0
- data/examples/showcase/config/lcp_ruby/presenters/save_filter_dialog.rb +17 -0
- data/examples/showcase/config/lcp_ruby/presenters/saved_filters.rb +94 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregate_items.rb +61 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregates.rb +115 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_aggregates_tiles.rb +44 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_amount_kanban.rb +31 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_arrays.rb +140 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_attachments.rb +66 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_audit_logs.rb +46 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_audited_records.rb +58 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_batch_tasks.rb +97 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_batch_tasks_archive.rb +52 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_business_units.rb +43 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_conditions.rb +195 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_contacts.rb +82 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_render_with.rb +50 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_sections.rb +88 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_custom_zones_main.rb +75 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_divisions.rb +53 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_extensibility.rb +48 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_card.rb +34 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_table.rb +128 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_fields_tiles.rb +42 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_form_action_dialog.rb +27 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_form_actions.rb +132 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_form_actions_overflow.rb +76 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_forms.rb +110 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_grades.rb +38 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_hr_employees.rb +84 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_item_classes.rb +120 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_job_executions.rb +150 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_memos.rb +96 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_models.rb +112 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_organizations.rb +106 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_people.rb +97 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_permissions.rb +84 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_positioning.rb +61 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_recipes.rb +105 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_recipes_raw.rb +32 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_reports.rb +109 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_school_classes.rb +34 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_searches.rb +226 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_sequences.rb +67 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete.rb +88 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete_archive.rb +52 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_soft_delete_items.rb +47 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_students.rb +33 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_type_defaults.rb +92 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_userstamps.rb +76 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_virtual_fields.rb +104 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_workflow_admin.rb +24 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_workflow_kanban.rb +36 -0
- data/examples/showcase/config/lcp_ruby/presenters/showcase_workflows.rb +103 -0
- data/examples/showcase/config/lcp_ruby/presenters/tags.rb +35 -0
- data/examples/showcase/config/lcp_ruby/presenters/users.rb +61 -0
- data/examples/showcase/config/lcp_ruby/presenters/weather_stations.rb +60 -0
- data/examples/showcase/config/lcp_ruby/presenters/workflow_audit_logs.rb +76 -0
- data/examples/showcase/config/lcp_ruby/presenters/workflow_request_audit_zone.rb +28 -0
- data/examples/showcase/config/lcp_ruby/theme.yml +2 -0
- data/examples/showcase/config/lcp_ruby/types/currency.yml +15 -0
- data/examples/showcase/config/lcp_ruby/types/percentage.yml +16 -0
- data/examples/showcase/config/lcp_ruby/types/rating.yml +20 -0
- data/examples/showcase/config/lcp_ruby/views/announcements.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/approval_requests.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/approval_steps.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/approval_tasks.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/articles.yml +12 -0
- data/examples/showcase/config/lcp_ruby/views/authors.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/batch_operation_items.yml +6 -0
- data/examples/showcase/config/lcp_ruby/views/batch_operations.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/categories.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/custom_fields.rb +8 -0
- data/examples/showcase/config/lcp_ruby/views/dashboard.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/department_explorer.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/departments.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/employee_overview.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/employees.yml +12 -0
- data/examples/showcase/config/lcp_ruby/views/export_logs.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/export_profiles.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/features.yml +19 -0
- data/examples/showcase/config/lcp_ruby/views/group_memberships.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/group_role_mappings.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/groups.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/host_inventory_items.yml +16 -0
- data/examples/showcase/config/lcp_ruby/views/hr_turnover_dashboard.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/import_profiles.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/import_rows.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/lcp_error_logs.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/monitoring.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/my_api_tokens.rb +9 -0
- data/examples/showcase/config/lcp_ruby/views/page_configs.yml +11 -0
- data/examples/showcase/config/lcp_ruby/views/permission_configs.yml +11 -0
- data/examples/showcase/config/lcp_ruby/views/pipeline_stages.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/pipelines.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/platform_profiles.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/projects.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/roles.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/saved_filters.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_aggregate_items.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_aggregates.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_arrays.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_attachments.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_audit_logs.yml +11 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_audited_records.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_batch_tasks.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_business_units.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_conditions.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_contacts.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_custom_render_with.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_custom_sections.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_custom_zones.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_divisions.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_extensibility.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_fields.yml +13 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_form_actions.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_forms.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_grades.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_hr_employees.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_item_classes.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_job_executions.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_memos.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_models.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_organizations.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_people.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_permissions.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_positioning.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_recipes.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_reports.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_school_classes.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_searches.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_sequences.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_soft_delete.yml +10 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_soft_delete_items.yml +8 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_students.yml +9 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_userstamps.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_virtual_fields.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/showcase_workflows.yml +16 -0
- data/examples/showcase/config/lcp_ruby/views/tags.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/users.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/weather_stations.yml +7 -0
- data/examples/showcase/config/lcp_ruby/views/workflow_audit_logs.yml +9 -0
- data/examples/showcase/config/lcp_ruby/workflows/showcase_form_action.rb +48 -0
- data/examples/showcase/config/lcp_ruby/workflows/showcase_workflow.rb +199 -0
- data/examples/showcase/config/locales/cs.yml +2818 -0
- data/examples/showcase/config/locales/en.yml +67 -0
- data/examples/showcase/config/locales/lcp_ruby/api_tokens.cs.yml +46 -0
- data/examples/showcase/config/locales/lcp_ruby/api_tokens.en.yml +49 -0
- data/examples/showcase/config/routes.rb +21 -0
- data/examples/showcase/config/storage.yml +3 -0
- data/examples/showcase/config.ru +2 -0
- data/examples/showcase/db/migrate/20260219124321_create_active_storage_tables.active_storage.rb +57 -0
- data/examples/showcase/db/migrate/20260220072723_create_lcp_ruby_users.rb +42 -0
- data/examples/showcase/db/migrate/20260406120000_create_host_inventory_items.rb +32 -0
- data/examples/showcase/db/migrate/20260428134542_add_oidc_columns_to_lcp_ruby_users.rb +13 -0
- data/examples/showcase/db/migrate/20260502120000_create_platform_profiles.rb +19 -0
- data/examples/showcase/db/schema.rb +1222 -0
- data/examples/showcase/db/seeds.rb +3085 -0
- data/examples/showcase/erd.md +567 -0
- data/examples/showcase/test/fixtures/import_articles.csv +4 -0
- data/examples/showcase/test/fixtures/import_employees.csv +4 -0
- data/examples/todo/Gemfile +8 -0
- data/examples/todo/Gemfile.lock +415 -0
- data/examples/todo/Rakefile +2 -0
- data/examples/todo/app/assets/config/manifest.js +1 -0
- data/examples/todo/app/controllers/application_controller.rb +6 -0
- data/examples/todo/app/lcp_services/defaults/one_week_from_now.rb +11 -0
- data/examples/todo/bin/rails +4 -0
- data/examples/todo/bin/rake +4 -0
- data/examples/todo/config/application.rb +31 -0
- data/examples/todo/config/boot.rb +2 -0
- data/examples/todo/config/database.yml +12 -0
- data/examples/todo/config/environment.rb +2 -0
- data/examples/todo/config/lcp_ruby/models/todo_item.yml +67 -0
- data/examples/todo/config/lcp_ruby/models/todo_list.yml +49 -0
- data/examples/todo/config/lcp_ruby/permissions/default.yml +10 -0
- data/examples/todo/config/lcp_ruby/permissions/todo_item.yml +21 -0
- data/examples/todo/config/lcp_ruby/presenters/todo_item.yml +75 -0
- data/examples/todo/config/lcp_ruby/presenters/todo_list.yml +68 -0
- data/examples/todo/config/lcp_ruby/views/todo_items.yml +11 -0
- data/examples/todo/config/lcp_ruby/views/todo_lists.yml +9 -0
- data/examples/todo/config/routes.rb +4 -0
- data/examples/todo/config/storage.yml +3 -0
- data/examples/todo/config.ru +2 -0
- data/examples/todo/db/migrate/20260219103416_create_active_storage_tables.active_storage.rb +57 -0
- data/examples/todo/db/schema.rb +63 -0
- data/examples/todo/db/seeds.rb +24 -0
- data/examples/todo/erd.md +21 -0
- data/exe/lcp +33 -0
- data/lib/generators/lcp_ruby/agent_setup_generator.rb +102 -0
- data/lib/generators/lcp_ruby/api_tokens_generator.rb +102 -0
- data/lib/generators/lcp_ruby/auditing_generator.rb +54 -0
- data/lib/generators/lcp_ruby/background_jobs_generator.rb +61 -0
- data/lib/generators/lcp_ruby/batch_operations_generator.rb +62 -0
- data/lib/generators/lcp_ruby/claude_skills_generator.rb +47 -0
- data/lib/generators/lcp_ruby/custom_fields_generator.rb +54 -0
- data/lib/generators/lcp_ruby/dsl_to_yaml.rb +72 -0
- data/lib/generators/lcp_ruby/entity/color_palette.rb +22 -0
- data/lib/generators/lcp_ruby/entity/field_descriptor.rb +24 -0
- data/lib/generators/lcp_ruby/entity/field_token_parser.rb +101 -0
- data/lib/generators/lcp_ruby/entity/role_discovery.rb +45 -0
- data/lib/generators/lcp_ruby/entity_generator.rb +1104 -0
- data/lib/generators/lcp_ruby/export_generator.rb +71 -0
- data/lib/generators/lcp_ruby/format_support.rb +56 -0
- data/lib/generators/lcp_ruby/gapfree_sequences_generator.rb +64 -0
- data/lib/generators/lcp_ruby/groups_generator.rb +94 -0
- data/lib/generators/lcp_ruby/host_controller_generator.rb +202 -0
- data/lib/generators/lcp_ruby/import_generator.rb +96 -0
- data/lib/generators/lcp_ruby/install_auth_generator.rb +432 -0
- data/lib/generators/lcp_ruby/install_generator.rb +319 -0
- data/lib/generators/lcp_ruby/monitoring_generator.rb +58 -0
- data/lib/generators/lcp_ruby/oidc_role_mappings_generator.rb +60 -0
- data/lib/generators/lcp_ruby/pages_generator.rb +66 -0
- data/lib/generators/lcp_ruby/permission_source_generator.rb +66 -0
- data/lib/generators/lcp_ruby/role_model_generator.rb +73 -0
- data/lib/generators/lcp_ruby/saved_filters_generator.rb +62 -0
- data/lib/generators/lcp_ruby/templates/add_oidc_columns_to_lcp_ruby_users.rb.erb +13 -0
- data/lib/generators/lcp_ruby/templates/agent_setup/agents_md.md +3 -0
- data/lib/generators/lcp_ruby/templates/agent_setup/claude_md.md +12 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/create_dialog.rb +31 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/locales.en.yml +43 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/model.rb +23 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/permissions.yml +30 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/presenter.rb +38 -0
- data/lib/generators/lcp_ruby/templates/api_tokens/view_group.rb +13 -0
- data/lib/generators/lcp_ruby/templates/auditing/model.rb +34 -0
- data/lib/generators/lcp_ruby/templates/auditing/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/auditing/presenter.rb +41 -0
- data/lib/generators/lcp_ruby/templates/auditing/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/background_jobs/model.rb +59 -0
- data/lib/generators/lcp_ruby/templates/background_jobs/permissions.yml +22 -0
- data/lib/generators/lcp_ruby/templates/background_jobs/presenter.rb +82 -0
- data/lib/generators/lcp_ruby/templates/background_jobs/view_group.rb +9 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/item_model.rb +28 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/item_presenter.rb +43 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/model.rb +42 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/permissions.yml +44 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/presenter.rb +61 -0
- data/lib/generators/lcp_ruby/templates/batch_operations/view_group.rb +9 -0
- data/lib/generators/lcp_ruby/templates/create_lcp_ruby_users.rb.erb +50 -0
- data/lib/generators/lcp_ruby/templates/custom_fields/model.rb +60 -0
- data/lib/generators/lcp_ruby/templates/custom_fields/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/custom_fields/presenter.rb +128 -0
- data/lib/generators/lcp_ruby/templates/custom_fields/view_group.rb +9 -0
- data/lib/generators/lcp_ruby/templates/entity/model.rb +42 -0
- data/lib/generators/lcp_ruby/templates/entity/permissions.yml +20 -0
- data/lib/generators/lcp_ruby/templates/entity/presenter.rb +55 -0
- data/lib/generators/lcp_ruby/templates/entity/view_group.rb +8 -0
- data/lib/generators/lcp_ruby/templates/export/export_log_model.rb +30 -0
- data/lib/generators/lcp_ruby/templates/export/export_log_permissions.yml +24 -0
- data/lib/generators/lcp_ruby/templates/export/export_logs_presenter.rb +51 -0
- data/lib/generators/lcp_ruby/templates/export/export_profile_model.rb +28 -0
- data/lib/generators/lcp_ruby/templates/export/export_profile_permissions.yml +26 -0
- data/lib/generators/lcp_ruby/templates/export/export_profiles_presenter.rb +59 -0
- data/lib/generators/lcp_ruby/templates/gapfree_sequences/model.rb +18 -0
- data/lib/generators/lcp_ruby/templates/gapfree_sequences/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/gapfree_sequences/presenter.rb +51 -0
- data/lib/generators/lcp_ruby/templates/gapfree_sequences/view_group.rb +9 -0
- data/lib/generators/lcp_ruby/templates/groups/group_membership_model.rb +17 -0
- data/lib/generators/lcp_ruby/templates/groups/group_model.rb +28 -0
- data/lib/generators/lcp_ruby/templates/groups/group_permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/groups/group_presenter.rb +53 -0
- data/lib/generators/lcp_ruby/templates/groups/group_role_mapping_model.rb +15 -0
- data/lib/generators/lcp_ruby/templates/groups/group_view_group.rb +9 -0
- data/lib/generators/lcp_ruby/templates/host_controller/controller.rb.erb +131 -0
- data/lib/generators/lcp_ruby/templates/import/data_import_job.yml +7 -0
- data/lib/generators/lcp_ruby/templates/import/import_profile_model.rb +30 -0
- data/lib/generators/lcp_ruby/templates/import/import_profile_permissions.yml +29 -0
- data/lib/generators/lcp_ruby/templates/import/import_profiles_presenter.rb +57 -0
- data/lib/generators/lcp_ruby/templates/import/import_row_model.rb +32 -0
- data/lib/generators/lcp_ruby/templates/import/import_row_permissions.yml +11 -0
- data/lib/generators/lcp_ruby/templates/import/import_rows_presenter.rb +35 -0
- data/lib/generators/lcp_ruby/templates/install/default_permissions.yml +26 -0
- data/lib/generators/lcp_ruby/templates/install/menu.yml.tt +71 -0
- data/lib/generators/lcp_ruby/templates/install/model.rb +20 -0
- data/lib/generators/lcp_ruby/templates/install/permissions.yml +25 -0
- data/lib/generators/lcp_ruby/templates/install/presenter.rb +44 -0
- data/lib/generators/lcp_ruby/templates/install/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/install_auth/oidc/entra.yml.erb +46 -0
- data/lib/generators/lcp_ruby/templates/install_auth/oidc/generic.yml.erb +55 -0
- data/lib/generators/lcp_ruby/templates/install_auth/oidc/google.yml.erb +42 -0
- data/lib/generators/lcp_ruby/templates/install_auth/oidc/keycloak.yml.erb +45 -0
- data/lib/generators/lcp_ruby/templates/install_auth/oidc/okta.yml.erb +45 -0
- data/lib/generators/lcp_ruby/templates/install_auth/user.rb +36 -0
- data/lib/generators/lcp_ruby/templates/monitoring/model.rb +34 -0
- data/lib/generators/lcp_ruby/templates/monitoring/model_extension.rb +12 -0
- data/lib/generators/lcp_ruby/templates/monitoring/page.yml +49 -0
- data/lib/generators/lcp_ruby/templates/monitoring/permissions.yml +8 -0
- data/lib/generators/lcp_ruby/templates/monitoring/presenter.rb +44 -0
- data/lib/generators/lcp_ruby/templates/oidc_role_mappings/locales.en.yml +15 -0
- data/lib/generators/lcp_ruby/templates/oidc_role_mappings/model.rb +32 -0
- data/lib/generators/lcp_ruby/templates/oidc_role_mappings/permissions.yml +21 -0
- data/lib/generators/lcp_ruby/templates/oidc_role_mappings/presenter.rb +41 -0
- data/lib/generators/lcp_ruby/templates/pages/model.rb +19 -0
- data/lib/generators/lcp_ruby/templates/pages/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/pages/presenter.rb +51 -0
- data/lib/generators/lcp_ruby/templates/pages/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/permission_source/model.rb +21 -0
- data/lib/generators/lcp_ruby/templates/permission_source/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/permission_source/presenter.rb +51 -0
- data/lib/generators/lcp_ruby/templates/permission_source/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/role_model/model.rb +24 -0
- data/lib/generators/lcp_ruby/templates/role_model/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/role_model/presenter.rb +62 -0
- data/lib/generators/lcp_ruby/templates/role_model/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/saved_filters/model.rb +51 -0
- data/lib/generators/lcp_ruby/templates/saved_filters/permissions.yml +44 -0
- data/lib/generators/lcp_ruby/templates/saved_filters/presenter.rb +96 -0
- data/lib/generators/lcp_ruby/templates/saved_filters/save_dialog_presenter.rb +19 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/request_model.rb +50 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/request_permissions.yml +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/request_presenter.rb +44 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/request_view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/step_model.rb +36 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/step_permissions.yml +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/task_model.rb +34 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/task_permissions.yml +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/task_presenter.rb +39 -0
- data/lib/generators/lcp_ruby/templates/workflow_approvals/task_view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_audit_log/model.rb +48 -0
- data/lib/generators/lcp_ruby/templates/workflow_audit_log/permissions.yml +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_audit_log/presenter.rb +44 -0
- data/lib/generators/lcp_ruby/templates/workflow_audit_log/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/templates/workflow_definition/model.rb +43 -0
- data/lib/generators/lcp_ruby/templates/workflow_definition/permissions.yml +19 -0
- data/lib/generators/lcp_ruby/templates/workflow_definition/presenter.rb +70 -0
- data/lib/generators/lcp_ruby/templates/workflow_definition/view_group.rb +10 -0
- data/lib/generators/lcp_ruby/workflow_approvals_generator.rb +93 -0
- data/lib/generators/lcp_ruby/workflow_audit_log_generator.rb +54 -0
- data/lib/generators/lcp_ruby/workflow_definition_generator.rb +77 -0
- data/lib/lcp.rb +6 -0
- data/lib/lcp_ruby/actions/action_executor.rb +66 -0
- data/lib/lcp_ruby/actions/action_registry.rb +48 -0
- data/lib/lcp_ruby/actions/api_tokens/revoke.rb +13 -0
- data/lib/lcp_ruby/actions/base_action.rb +79 -0
- data/lib/lcp_ruby/actions/form_action_pipeline.rb +138 -0
- data/lib/lcp_ruby/aggregates/query_builder.rb +6 -0
- data/lib/lcp_ruby/api_tokens/model_extension.rb +41 -0
- data/lib/lcp_ruby/api_tokens/resolver_registry.rb +53 -0
- data/lib/lcp_ruby/api_tokens/token_generator.rb +27 -0
- data/lib/lcp_ruby/api_tokens/verifier.rb +38 -0
- data/lib/lcp_ruby/app_template.rb +181 -0
- data/lib/lcp_ruby/array_query.rb +120 -0
- data/lib/lcp_ruby/asset_copier.rb +62 -0
- data/lib/lcp_ruby/association_fk_type.rb +191 -0
- data/lib/lcp_ruby/association_join_column.rb +28 -0
- data/lib/lcp_ruby/association_options_builder.rb +231 -0
- data/lib/lcp_ruby/auditing/audit_writer.rb +258 -0
- data/lib/lcp_ruby/auditing/contract_validator.rb +95 -0
- data/lib/lcp_ruby/auditing/registry.rb +29 -0
- data/lib/lcp_ruby/auditing/setup.rb +49 -0
- data/lib/lcp_ruby/authentication/audit_subscriber.rb +51 -0
- data/lib/lcp_ruby/authentication/bearer_jwt_verifier.rb +139 -0
- data/lib/lcp_ruby/authentication/devise_setup.rb +47 -0
- data/lib/lcp_ruby/authentication/errors.rb +27 -0
- data/lib/lcp_ruby/authentication/http_fetcher.rb +36 -0
- data/lib/lcp_ruby/authentication/jwks_cache.rb +91 -0
- data/lib/lcp_ruby/authentication/oidc_bearer_resolver.rb +84 -0
- data/lib/lcp_ruby/authentication/omniauth_builder.rb +147 -0
- data/lib/lcp_ruby/authentication/provider.rb +108 -0
- data/lib/lcp_ruby/authentication/provider_registry.rb +227 -0
- data/lib/lcp_ruby/authentication/role_mapper.rb +94 -0
- data/lib/lcp_ruby/authentication/test_support.rb +257 -0
- data/lib/lcp_ruby/authentication/user_resolver.rb +169 -0
- data/lib/lcp_ruby/authentication.rb +40 -0
- data/lib/lcp_ruby/authorization/association_lookup.rb +56 -0
- data/lib/lcp_ruby/authorization/authorization_error.rb +12 -0
- data/lib/lcp_ruby/authorization/cache.rb +89 -0
- data/lib/lcp_ruby/authorization/codes.rb +17 -0
- data/lib/lcp_ruby/authorization/impersonated_user.rb +29 -0
- data/lib/lcp_ruby/authorization/includes_hint.rb +110 -0
- data/lib/lcp_ruby/authorization/inherited_parent_validator.rb +142 -0
- data/lib/lcp_ruby/authorization/invariant_check/configuration.rb +132 -0
- data/lib/lcp_ruby/authorization/invariant_error.rb +15 -0
- data/lib/lcp_ruby/authorization/misconfigured_page_error.rb +30 -0
- data/lib/lcp_ruby/authorization/page_gate.rb +57 -0
- data/lib/lcp_ruby/authorization/permission_evaluator.rb +343 -0
- data/lib/lcp_ruby/authorization/policy_factory.rb +91 -0
- data/lib/lcp_ruby/authorization/runtime_invariant_validator.rb +421 -0
- data/lib/lcp_ruby/authorization/scope_builder.rb +227 -0
- data/lib/lcp_ruby/authorization/scope_resolver.rb +28 -0
- data/lib/lcp_ruby/authorized_controller.rb +44 -0
- data/lib/lcp_ruby/background_jobs/base_handler.rb +113 -0
- data/lib/lcp_ruby/background_jobs/change_handler.rb +17 -0
- data/lib/lcp_ruby/background_jobs/contract.rb +16 -0
- data/lib/lcp_ruby/background_jobs/contract_validator.rb +112 -0
- data/lib/lcp_ruby/background_jobs/declarative/base_action.rb +11 -0
- data/lib/lcp_ruby/background_jobs/declarative/call_webhook_action.rb +174 -0
- data/lib/lcp_ruby/background_jobs/declarative/fire_event_action.rb +24 -0
- data/lib/lcp_ruby/background_jobs/declarative/registry.rb +34 -0
- data/lib/lcp_ruby/background_jobs/declarative/run_scope_action.rb +98 -0
- data/lib/lcp_ruby/background_jobs/declarative/send_notification_action.rb +13 -0
- data/lib/lcp_ruby/background_jobs/definition.rb +134 -0
- data/lib/lcp_ruby/background_jobs/enqueue.rb +83 -0
- data/lib/lcp_ruby/background_jobs/errors.rb +9 -0
- data/lib/lcp_ruby/background_jobs/executor_job.rb +111 -0
- data/lib/lcp_ruby/background_jobs/handler_factory.rb +46 -0
- data/lib/lcp_ruby/background_jobs/host_source.rb +33 -0
- data/lib/lcp_ruby/background_jobs/model_source.rb +93 -0
- data/lib/lcp_ruby/background_jobs/registry.rb +81 -0
- data/lib/lcp_ruby/background_jobs/resolver.rb +29 -0
- data/lib/lcp_ruby/background_jobs/schedule_adapter.rb +14 -0
- data/lib/lcp_ruby/background_jobs/setup.rb +145 -0
- data/lib/lcp_ruby/background_jobs/static_source.rb +19 -0
- data/lib/lcp_ruby/background_jobs/steps_executor.rb +52 -0
- data/lib/lcp_ruby/background_jobs/triggers/event_trigger.rb +97 -0
- data/lib/lcp_ruby/background_jobs/triggers/trigger_installer.rb +20 -0
- data/lib/lcp_ruby/background_jobs/unique_key_builder.rb +31 -0
- data/lib/lcp_ruby/batch_actions/base_service.rb +70 -0
- data/lib/lcp_ruby/batch_actions/batch_action_handler.rb +200 -0
- data/lib/lcp_ruby/batch_actions/custom_action_dispatcher.rb +133 -0
- data/lib/lcp_ruby/batch_actions/destroy_service.rb +37 -0
- data/lib/lcp_ruby/batch_actions/permanently_destroy_service.rb +33 -0
- data/lib/lcp_ruby/batch_actions/restore_service.rb +33 -0
- data/lib/lcp_ruby/batch_actions.rb +5 -0
- data/lib/lcp_ruby/bulk_updater.rb +25 -0
- data/lib/lcp_ruby/cli/docs_command.rb +29 -0
- data/lib/lcp_ruby/cli/examples_command.rb +29 -0
- data/lib/lcp_ruby/cli/new_command.rb +509 -0
- data/lib/lcp_ruby/cli/run_command.rb +155 -0
- data/lib/lcp_ruby/cli/skills_command.rb +54 -0
- data/lib/lcp_ruby/cli.rb +74 -0
- data/lib/lcp_ruby/condition_evaluator.rb +366 -0
- data/lib/lcp_ruby/condition_service_registry.rb +58 -0
- data/lib/lcp_ruby/condition_services/current_user_role.rb +28 -0
- data/lib/lcp_ruby/condition_services/feature_flag.rb +63 -0
- data/lib/lcp_ruby/condition_services/impersonating.rb +24 -0
- data/lib/lcp_ruby/conditions/validator.rb +35 -0
- data/lib/lcp_ruby/configuration.rb +431 -0
- data/lib/lcp_ruby/controller/authentication.rb +118 -0
- data/lib/lcp_ruby/controller/authorization.rb +198 -0
- data/lib/lcp_ruby/controller/bearer_authentication.rb +76 -0
- data/lib/lcp_ruby/controller/crud_helpers.rb +233 -0
- data/lib/lcp_ruby/controller/error_handling.rb +94 -0
- data/lib/lcp_ruby/controller/impersonation.rb +70 -0
- data/lib/lcp_ruby/controller/locale_binding.rb +62 -0
- data/lib/lcp_ruby/controller/path_helpers.rb +125 -0
- data/lib/lcp_ruby/controller/presenter_setup.rb +89 -0
- data/lib/lcp_ruby/controller/search.rb +321 -0
- data/lib/lcp_ruby/controller/view_helpers.rb +105 -0
- data/lib/lcp_ruby/current.rb +13 -0
- data/lib/lcp_ruby/custom_fields/applicator.rb +194 -0
- data/lib/lcp_ruby/custom_fields/contract_validator.rb +77 -0
- data/lib/lcp_ruby/custom_fields/definition_change_handler.rb +21 -0
- data/lib/lcp_ruby/custom_fields/query.rb +112 -0
- data/lib/lcp_ruby/custom_fields/registry.rb +70 -0
- data/lib/lcp_ruby/custom_fields/setup.rb +58 -0
- data/lib/lcp_ruby/custom_fields/utils.rb +40 -0
- data/lib/lcp_ruby/custom_fields.rb +9 -0
- data/lib/lcp_ruby/data_source/api_error_placeholder.rb +47 -0
- data/lib/lcp_ruby/data_source/api_filter_translator.rb +72 -0
- data/lib/lcp_ruby/data_source/api_model_concern.rb +131 -0
- data/lib/lcp_ruby/data_source/api_preloader.rb +44 -0
- data/lib/lcp_ruby/data_source/base.rb +85 -0
- data/lib/lcp_ruby/data_source/cached_wrapper.rb +107 -0
- data/lib/lcp_ruby/data_source/host.rb +71 -0
- data/lib/lcp_ruby/data_source/registry.rb +39 -0
- data/lib/lcp_ruby/data_source/resilient_wrapper.rb +67 -0
- data/lib/lcp_ruby/data_source/rest_json.rb +247 -0
- data/lib/lcp_ruby/data_source/setup.rb +57 -0
- data/lib/lcp_ruby/dev_toolbar.rb +8 -0
- data/lib/lcp_ruby/display/base_renderer.rb +21 -0
- data/lib/lcp_ruby/display/count_badge.rb +11 -0
- data/lib/lcp_ruby/display/icon_badge.rb +17 -0
- data/lib/lcp_ruby/display/renderer_registry.rb +138 -0
- data/lib/lcp_ruby/display/renderers/attachment_link.rb +26 -0
- data/lib/lcp_ruby/display/renderers/attachment_list.rb +32 -0
- data/lib/lcp_ruby/display/renderers/attachment_preview.rb +26 -0
- data/lib/lcp_ruby/display/renderers/avatar.rb +14 -0
- data/lib/lcp_ruby/display/renderers/badge.rb +43 -0
- data/lib/lcp_ruby/display/renderers/boolean_icon.rb +54 -0
- data/lib/lcp_ruby/display/renderers/code.rb +39 -0
- data/lib/lcp_ruby/display/renderers/collection.rb +34 -0
- data/lib/lcp_ruby/display/renderers/color_swatch.rb +25 -0
- data/lib/lcp_ruby/display/renderers/concerns/attachment_helpers.rb +73 -0
- data/lib/lcp_ruby/display/renderers/concerns/workflow_helpers.rb +35 -0
- data/lib/lcp_ruby/display/renderers/copy_code.rb +33 -0
- data/lib/lcp_ruby/display/renderers/currency.rb +14 -0
- data/lib/lcp_ruby/display/renderers/date.rb +17 -0
- data/lib/lcp_ruby/display/renderers/datetime.rb +17 -0
- data/lib/lcp_ruby/display/renderers/email_link.rb +15 -0
- data/lib/lcp_ruby/display/renderers/file_size.rb +11 -0
- data/lib/lcp_ruby/display/renderers/heading.rb +11 -0
- data/lib/lcp_ruby/display/renderers/image.rb +17 -0
- data/lib/lcp_ruby/display/renderers/internal_link.rb +23 -0
- data/lib/lcp_ruby/display/renderers/link.rb +15 -0
- data/lib/lcp_ruby/display/renderers/link_list.rb +90 -0
- data/lib/lcp_ruby/display/renderers/markdown.rb +33 -0
- data/lib/lcp_ruby/display/renderers/number.rb +14 -0
- data/lib/lcp_ruby/display/renderers/percentage.rb +12 -0
- data/lib/lcp_ruby/display/renderers/phone_link.rb +15 -0
- data/lib/lcp_ruby/display/renderers/progress_bar.rb +15 -0
- data/lib/lcp_ruby/display/renderers/rating.rb +15 -0
- data/lib/lcp_ruby/display/renderers/record_link.rb +101 -0
- data/lib/lcp_ruby/display/renderers/relative_date.rb +15 -0
- data/lib/lcp_ruby/display/renderers/rich_text.rb +15 -0
- data/lib/lcp_ruby/display/renderers/text.rb +12 -0
- data/lib/lcp_ruby/display/renderers/truncate.rb +17 -0
- data/lib/lcp_ruby/display/renderers/url_link.rb +22 -0
- data/lib/lcp_ruby/display/renderers/workflow_badge.rb +37 -0
- data/lib/lcp_ruby/display/renderers/workflow_timeline.rb +173 -0
- data/lib/lcp_ruby/display/renderers.rb +3 -0
- data/lib/lcp_ruby/display/text_badge.rb +15 -0
- data/lib/lcp_ruby/dsl/condition_builder.rb +190 -0
- data/lib/lcp_ruby/dsl/dsl_loader.rb +365 -0
- data/lib/lcp_ruby/dsl/field_builder.rb +35 -0
- data/lib/lcp_ruby/dsl/job_builder.rb +92 -0
- data/lib/lcp_ruby/dsl/model_builder.rb +544 -0
- data/lib/lcp_ruby/dsl/presenter_builder.rb +1272 -0
- data/lib/lcp_ruby/dsl/source_location_capture.rb +52 -0
- data/lib/lcp_ruby/dsl/type_builder.rb +88 -0
- data/lib/lcp_ruby/dsl/view_group_builder.rb +92 -0
- data/lib/lcp_ruby/dsl/workflow_builder.rb +319 -0
- data/lib/lcp_ruby/dynamic.rb +7 -0
- data/lib/lcp_ruby/dynamic_references/resolver.rb +154 -0
- data/lib/lcp_ruby/dynamic_references/validator.rb +92 -0
- data/lib/lcp_ruby/embed_providers/base.rb +18 -0
- data/lib/lcp_ruby/embed_providers/grafana.rb +38 -0
- data/lib/lcp_ruby/embed_providers/metabase.rb +37 -0
- data/lib/lcp_ruby/engine.rb +680 -0
- data/lib/lcp_ruby/events/async_handler_job.rb +21 -0
- data/lib/lcp_ruby/events/dispatcher.rb +52 -0
- data/lib/lcp_ruby/events/handler_base.rb +51 -0
- data/lib/lcp_ruby/events/handler_registry.rb +49 -0
- data/lib/lcp_ruby/export/data_generator.rb +158 -0
- data/lib/lcp_ruby/export/export_handler.rb +315 -0
- data/lib/lcp_ruby/export/field_tree_builder.rb +219 -0
- data/lib/lcp_ruby/export/setup.rb +94 -0
- data/lib/lcp_ruby/export/value_formatter.rb +223 -0
- data/lib/lcp_ruby/export.rb +9 -0
- data/lib/lcp_ruby/gem_paths.rb +51 -0
- data/lib/lcp_ruby/generators/entity_menu_writer.rb +258 -0
- data/lib/lcp_ruby/generators/feature_registry.rb +208 -0
- data/lib/lcp_ruby/generators/prerequisites.rb +90 -0
- data/lib/lcp_ruby/grouped_query/builder.rb +206 -0
- data/lib/lcp_ruby/grouped_query/result_wrapper.rb +72 -0
- data/lib/lcp_ruby/grouped_query/row.rb +31 -0
- data/lib/lcp_ruby/groups/change_handler.rb +18 -0
- data/lib/lcp_ruby/groups/contract.rb +42 -0
- data/lib/lcp_ruby/groups/contract_validator.rb +110 -0
- data/lib/lcp_ruby/groups/host_loader.rb +54 -0
- data/lib/lcp_ruby/groups/model_loader.rb +186 -0
- data/lib/lcp_ruby/groups/registry.rb +113 -0
- data/lib/lcp_ruby/groups/setup.rb +129 -0
- data/lib/lcp_ruby/groups/yaml_loader.rb +97 -0
- data/lib/lcp_ruby/hash_utils.rb +42 -0
- data/lib/lcp_ruby/i18n_check/configuration.rb +104 -0
- data/lib/lcp_ruby/i18n_check/heuristics.rb +26 -0
- data/lib/lcp_ruby/i18n_check/key_deriver.rb +136 -0
- data/lib/lcp_ruby/i18n_check/offense.rb +29 -0
- data/lib/lcp_ruby/i18n_check/registry_walker.rb +621 -0
- data/lib/lcp_ruby/i18n_check/reporter.rb +96 -0
- data/lib/lcp_ruby/i18n_check/runner.rb +46 -0
- data/lib/lcp_ruby/i18n_check.rb +15 -0
- data/lib/lcp_ruby/i18n_lint.rb +145 -0
- data/lib/lcp_ruby/import/auto_mapper.rb +98 -0
- data/lib/lcp_ruby/import/field_tree_builder.rb +178 -0
- data/lib/lcp_ruby/import/file_parser.rb +223 -0
- data/lib/lcp_ruby/import/import_dialog_handler.rb +410 -0
- data/lib/lcp_ruby/import/import_job_handler.rb +224 -0
- data/lib/lcp_ruby/import/row_processor.rb +281 -0
- data/lib/lcp_ruby/import/setup.rb +277 -0
- data/lib/lcp_ruby/import/value_coercer.rb +143 -0
- data/lib/lcp_ruby/import.rb +14 -0
- data/lib/lcp_ruby/json_item_wrapper.rb +152 -0
- data/lib/lcp_ruby/kanban/board.rb +28 -0
- data/lib/lcp_ruby/kanban/column.rb +28 -0
- data/lib/lcp_ruby/kanban/default_provider.rb +376 -0
- data/lib/lcp_ruby/kanban/host_provider.rb +94 -0
- data/lib/lcp_ruby/kanban/move_result.rb +52 -0
- data/lib/lcp_ruby/kanban/provider_test_harness.rb +54 -0
- data/lib/lcp_ruby/kanban/swimlane.rb +21 -0
- data/lib/lcp_ruby/menu.rb +46 -0
- data/lib/lcp_ruby/metadata/aggregate_definition.rb +6 -0
- data/lib/lcp_ruby/metadata/association_definition.rb +196 -0
- data/lib/lcp_ruby/metadata/auth_validator.rb +222 -0
- data/lib/lcp_ruby/metadata/configuration_validator.rb +7958 -0
- data/lib/lcp_ruby/metadata/contract_result.rb +9 -0
- data/lib/lcp_ruby/metadata/display_template_definition.rb +77 -0
- data/lib/lcp_ruby/metadata/enum_label_resolver.rb +27 -0
- data/lib/lcp_ruby/metadata/erd_generator.rb +274 -0
- data/lib/lcp_ruby/metadata/event_definition.rb +55 -0
- data/lib/lcp_ruby/metadata/field_definition.rb +267 -0
- data/lib/lcp_ruby/metadata/group_definition.rb +31 -0
- data/lib/lcp_ruby/metadata/i18n_label.rb +29 -0
- data/lib/lcp_ruby/metadata/loader.rb +916 -0
- data/lib/lcp_ruby/metadata/menu_definition.rb +116 -0
- data/lib/lcp_ruby/metadata/menu_item.rb +792 -0
- data/lib/lcp_ruby/metadata/menu_item_resolver.rb +105 -0
- data/lib/lcp_ruby/metadata/model_definition.rb +612 -0
- data/lib/lcp_ruby/metadata/model_hash_merger.rb +88 -0
- data/lib/lcp_ruby/metadata/model_inheritance_resolver.rb +165 -0
- data/lib/lcp_ruby/metadata/page_definition.rb +245 -0
- data/lib/lcp_ruby/metadata/path_template.rb +231 -0
- data/lib/lcp_ruby/metadata/permission_definition.rb +237 -0
- data/lib/lcp_ruby/metadata/permission_merger.rb +81 -0
- data/lib/lcp_ruby/metadata/presenter_definition.rb +689 -0
- data/lib/lcp_ruby/metadata/reserved_names.rb +79 -0
- data/lib/lcp_ruby/metadata/responsive_policy.rb +95 -0
- data/lib/lcp_ruby/metadata/schema_validator.rb +172 -0
- data/lib/lcp_ruby/metadata/validation_definition.rb +69 -0
- data/lib/lcp_ruby/metadata/view_group_definition.rb +208 -0
- data/lib/lcp_ruby/metadata/virtual_column_definition.rb +154 -0
- data/lib/lcp_ruby/metadata/zone_definition.rb +423 -0
- data/lib/lcp_ruby/metrics/collector.rb +123 -0
- data/lib/lcp_ruby/metrics/collector_registry.rb +57 -0
- data/lib/lcp_ruby/metrics/error_recorder.rb +70 -0
- data/lib/lcp_ruby/metrics/fingerprint.rb +33 -0
- data/lib/lcp_ruby/metrics/json_query.rb +47 -0
- data/lib/lcp_ruby/metrics/metric_definitions.rb +105 -0
- data/lib/lcp_ruby/metrics/prometheus_check.rb +9 -0
- data/lib/lcp_ruby/metrics/rate_limiter.rb +99 -0
- data/lib/lcp_ruby/metrics/setup.rb +71 -0
- data/lib/lcp_ruby/metrics/subscriber.rb +126 -0
- data/lib/lcp_ruby/model_factory/aggregate_applicator.rb +14 -0
- data/lib/lcp_ruby/model_factory/api_association_applicator.rb +119 -0
- data/lib/lcp_ruby/model_factory/api_builder.rb +85 -0
- data/lib/lcp_ruby/model_factory/array_type.rb +78 -0
- data/lib/lcp_ruby/model_factory/array_type_applicator.rb +33 -0
- data/lib/lcp_ruby/model_factory/association_applicator.rb +201 -0
- data/lib/lcp_ruby/model_factory/attachment_applicator.rb +160 -0
- data/lib/lcp_ruby/model_factory/auditing_applicator.rb +72 -0
- data/lib/lcp_ruby/model_factory/builder.rb +235 -0
- data/lib/lcp_ruby/model_factory/callback_applicator.rb +63 -0
- data/lib/lcp_ruby/model_factory/computed_applicator.rb +55 -0
- data/lib/lcp_ruby/model_factory/default_applicator.rb +85 -0
- data/lib/lcp_ruby/model_factory/enum_applicator.rb +24 -0
- data/lib/lcp_ruby/model_factory/inherited_parent_validator_applicator.rb +39 -0
- data/lib/lcp_ruby/model_factory/label_method_builder.rb +82 -0
- data/lib/lcp_ruby/model_factory/managed_tracking.rb +71 -0
- data/lib/lcp_ruby/model_factory/positioning_applicator.rb +23 -0
- data/lib/lcp_ruby/model_factory/ransack_applicator.rb +66 -0
- data/lib/lcp_ruby/model_factory/registry.rb +33 -0
- data/lib/lcp_ruby/model_factory/schema_manager.rb +655 -0
- data/lib/lcp_ruby/model_factory/scope_applicator.rb +87 -0
- data/lib/lcp_ruby/model_factory/sequence_applicator.rb +173 -0
- data/lib/lcp_ruby/model_factory/service_accessor_applicator.rb +40 -0
- data/lib/lcp_ruby/model_factory/soft_delete_applicator.rb +141 -0
- data/lib/lcp_ruby/model_factory/transform_applicator.rb +51 -0
- data/lib/lcp_ruby/model_factory/tree_applicator.rb +239 -0
- data/lib/lcp_ruby/model_factory/userstamps_applicator.rb +73 -0
- data/lib/lcp_ruby/model_factory/validation_applicator.rb +319 -0
- data/lib/lcp_ruby/model_factory/virtual_column_applicator.rb +79 -0
- data/lib/lcp_ruby/model_factory/workflow_applicator.rb +141 -0
- data/lib/lcp_ruby/pages/change_handler.rb +15 -0
- data/lib/lcp_ruby/pages/contract_validator.rb +74 -0
- data/lib/lcp_ruby/pages/definition_validator.rb +42 -0
- data/lib/lcp_ruby/pages/filter_form.rb +200 -0
- data/lib/lcp_ruby/pages/filter_form_validator.rb +636 -0
- data/lib/lcp_ruby/pages/registry.rb +133 -0
- data/lib/lcp_ruby/pages/resolver.rb +32 -0
- data/lib/lcp_ruby/pages/scope_context_resolver.rb +37 -0
- data/lib/lcp_ruby/pages/scope_filter_set.rb +57 -0
- data/lib/lcp_ruby/pages/setup.rb +46 -0
- data/lib/lcp_ruby/path_utils.rb +12 -0
- data/lib/lcp_ruby/permissions/change_handler.rb +22 -0
- data/lib/lcp_ruby/permissions/contract_validator.rb +74 -0
- data/lib/lcp_ruby/permissions/definition_validator.rb +119 -0
- data/lib/lcp_ruby/permissions/registry.rb +135 -0
- data/lib/lcp_ruby/permissions/setup.rb +51 -0
- data/lib/lcp_ruby/permissions/source_resolver.rb +56 -0
- data/lib/lcp_ruby/presenter/action_set.rb +236 -0
- data/lib/lcp_ruby/presenter/breadcrumb_builder.rb +183 -0
- data/lib/lcp_ruby/presenter/breadcrumb_path_helper.rb +17 -0
- data/lib/lcp_ruby/presenter/column_set.rb +268 -0
- data/lib/lcp_ruby/presenter/enrichment.rb +136 -0
- data/lib/lcp_ruby/presenter/field_value_resolver.rb +237 -0
- data/lib/lcp_ruby/presenter/includes_resolver/association_dependency.rb +59 -0
- data/lib/lcp_ruby/presenter/includes_resolver/dependency_collector.rb +394 -0
- data/lib/lcp_ruby/presenter/includes_resolver/loading_strategy.rb +70 -0
- data/lib/lcp_ruby/presenter/includes_resolver/strategy_resolver.rb +123 -0
- data/lib/lcp_ruby/presenter/includes_resolver.rb +42 -0
- data/lib/lcp_ruby/presenter/layout_builder.rb +467 -0
- data/lib/lcp_ruby/presenter/link_resolver.rb +65 -0
- data/lib/lcp_ruby/presenter/metadata_lookup.rb +28 -0
- data/lib/lcp_ruby/presenter/resolver.rb +25 -0
- data/lib/lcp_ruby/record_aliases/metadata_checker.rb +213 -0
- data/lib/lcp_ruby/record_aliases/setup.rb +212 -0
- data/lib/lcp_ruby/reserved_route_segments.rb +37 -0
- data/lib/lcp_ruby/roles/change_handler.rb +11 -0
- data/lib/lcp_ruby/roles/contract_validator.rb +67 -0
- data/lib/lcp_ruby/roles/registry.rb +89 -0
- data/lib/lcp_ruby/roles/setup.rb +50 -0
- data/lib/lcp_ruby/routing/presenter_routes.rb +104 -0
- data/lib/lcp_ruby/saved_filters/change_handler.rb +13 -0
- data/lib/lcp_ruby/saved_filters/contract_validator.rb +85 -0
- data/lib/lcp_ruby/saved_filters/registry.rb +36 -0
- data/lib/lcp_ruby/saved_filters/resolver.rb +108 -0
- data/lib/lcp_ruby/saved_filters/setup.rb +42 -0
- data/lib/lcp_ruby/saved_filters/stale_field_validator.rb +84 -0
- data/lib/lcp_ruby/schemas/auth.json +208 -0
- data/lib/lcp_ruby/schemas/menu.json +338 -0
- data/lib/lcp_ruby/schemas/model.json +1161 -0
- data/lib/lcp_ruby/schemas/page.json +877 -0
- data/lib/lcp_ruby/schemas/permission.json +454 -0
- data/lib/lcp_ruby/schemas/presenter.json +2274 -0
- data/lib/lcp_ruby/schemas/theme.json +62 -0
- data/lib/lcp_ruby/schemas/type.json +146 -0
- data/lib/lcp_ruby/schemas/view_group.json +163 -0
- data/lib/lcp_ruby/search/custom_field_filter.rb +171 -0
- data/lib/lcp_ruby/search/custom_filter_interceptor.rb +40 -0
- data/lib/lcp_ruby/search/filter_metadata_builder.rb +409 -0
- data/lib/lcp_ruby/search/filter_param_builder.rb +177 -0
- data/lib/lcp_ruby/search/operator_registry.rb +79 -0
- data/lib/lcp_ruby/search/param_sanitizer.rb +25 -0
- data/lib/lcp_ruby/search/parameter_definition.rb +187 -0
- data/lib/lcp_ruby/search/parameterized_scope_applicator.rb +129 -0
- data/lib/lcp_ruby/search/query_builder.rb +143 -0
- data/lib/lcp_ruby/search/query_language_parser.rb +549 -0
- data/lib/lcp_ruby/search/query_language_serializer.rb +193 -0
- data/lib/lcp_ruby/search/quick_search.rb +162 -0
- data/lib/lcp_ruby/search/relative_date_expander.rb +57 -0
- data/lib/lcp_ruby/search_result.rb +70 -0
- data/lib/lcp_ruby/sequences/sequence_manager.rb +51 -0
- data/lib/lcp_ruby/services/accessors/json_field.rb +23 -0
- data/lib/lcp_ruby/services/built_in_accessors.rb +17 -0
- data/lib/lcp_ruby/services/built_in_defaults.rb +22 -0
- data/lib/lcp_ruby/services/built_in_transforms.rb +20 -0
- data/lib/lcp_ruby/services/checker.rb +133 -0
- data/lib/lcp_ruby/services/registry.rb +83 -0
- data/lib/lcp_ruby/skills_installer.rb +73 -0
- data/lib/lcp_ruby/sort/enum_sort_order.rb +38 -0
- data/lib/lcp_ruby/tasks/destroy_order_resolver.rb +57 -0
- data/lib/lcp_ruby/tasks/doctor.rb +294 -0
- data/lib/lcp_ruby/tasks/permission_resolve_formatter.rb +245 -0
- data/lib/lcp_ruby/types/built_in_types.rb +157 -0
- data/lib/lcp_ruby/types/transforms/base_transform.rb +11 -0
- data/lib/lcp_ruby/types/transforms/downcase.rb +11 -0
- data/lib/lcp_ruby/types/transforms/normalize_phone.rb +19 -0
- data/lib/lcp_ruby/types/transforms/normalize_url.rb +16 -0
- data/lib/lcp_ruby/types/transforms/strip.rb +11 -0
- data/lib/lcp_ruby/types/type_definition.rb +112 -0
- data/lib/lcp_ruby/types/type_registry.rb +75 -0
- data/lib/lcp_ruby/url_safety.rb +97 -0
- data/lib/lcp_ruby/user_snapshot.rb +15 -0
- data/lib/lcp_ruby/version.rb +3 -0
- data/lib/lcp_ruby/view_slots/registry.rb +71 -0
- data/lib/lcp_ruby/view_slots/slot_component.rb +22 -0
- data/lib/lcp_ruby/view_slots/slot_context.rb +20 -0
- data/lib/lcp_ruby/virtual_columns/builder.rb +234 -0
- data/lib/lcp_ruby/virtual_columns/collector.rb +186 -0
- data/lib/lcp_ruby/virtual_columns.rb +4 -0
- data/lib/lcp_ruby/virtual_fields/synthetic_marker.rb +17 -0
- data/lib/lcp_ruby/virtual_fields/types/array_of.rb +49 -0
- data/lib/lcp_ruby/virtual_fields/virtual_field.rb +107 -0
- data/lib/lcp_ruby/virtual_fields/virtual_form.rb +144 -0
- data/lib/lcp_ruby/widgets/chart_palette.rb +25 -0
- data/lib/lcp_ruby/widgets/chartkick_check.rb +9 -0
- data/lib/lcp_ruby/widgets/data_resolver.rb +676 -0
- data/lib/lcp_ruby/widgets/date_grouper.rb +54 -0
- data/lib/lcp_ruby/widgets/presenter_zone_resolver.rb +170 -0
- data/lib/lcp_ruby/widgets/record_source_resolver.rb +56 -0
- data/lib/lcp_ruby/widgets/scope_applicator.rb +187 -0
- data/lib/lcp_ruby/workflow/approval/activation_handler.rb +39 -0
- data/lib/lcp_ruby/workflow/approval/approval_definition.rb +117 -0
- data/lib/lcp_ruby/workflow/approval/approver_resolver.rb +98 -0
- data/lib/lcp_ruby/workflow/approval/cleanup_handler.rb +37 -0
- data/lib/lcp_ruby/workflow/approval/contract_validator.rb +96 -0
- data/lib/lcp_ruby/workflow/approval/data_builder.rb +53 -0
- data/lib/lcp_ruby/workflow/approval/discard_handler.rb +51 -0
- data/lib/lcp_ruby/workflow/approval/engine.rb +314 -0
- data/lib/lcp_ruby/workflow/approval/registry.rb +40 -0
- data/lib/lcp_ruby/workflow/approval/resolution_handler.rb +103 -0
- data/lib/lcp_ruby/workflow/approval/setup.rb +138 -0
- data/lib/lcp_ruby/workflow/approval/step_definition.rb +52 -0
- data/lib/lcp_ruby/workflow/approval/step_evaluator.rb +163 -0
- data/lib/lcp_ruby/workflow/approval/system_evaluator.rb +29 -0
- data/lib/lcp_ruby/workflow/approval/task_manager.rb +202 -0
- data/lib/lcp_ruby/workflow/audit_contract_validator.rb +64 -0
- data/lib/lcp_ruby/workflow/audit_registry.rb +24 -0
- data/lib/lcp_ruby/workflow/audit_writer.rb +51 -0
- data/lib/lcp_ruby/workflow/change_handler.rb +14 -0
- data/lib/lcp_ruby/workflow/contract.rb +21 -0
- data/lib/lcp_ruby/workflow/contract_validator.rb +44 -0
- data/lib/lcp_ruby/workflow/errors.rb +12 -0
- data/lib/lcp_ruby/workflow/host_source.rb +19 -0
- data/lib/lcp_ruby/workflow/mermaid_builder.rb +217 -0
- data/lib/lcp_ruby/workflow/model_source.rb +79 -0
- data/lib/lcp_ruby/workflow/registry.rb +113 -0
- data/lib/lcp_ruby/workflow/resolver.rb +32 -0
- data/lib/lcp_ruby/workflow/setup.rb +135 -0
- data/lib/lcp_ruby/workflow/state_definition.rb +59 -0
- data/lib/lcp_ruby/workflow/state_machine.rb +78 -0
- data/lib/lcp_ruby/workflow/static_source.rb +20 -0
- data/lib/lcp_ruby/workflow/transition_action_builder.rb +46 -0
- data/lib/lcp_ruby/workflow/transition_definition.rb +70 -0
- data/lib/lcp_ruby/workflow/transition_executor.rb +140 -0
- data/lib/lcp_ruby/workflow/transition_label_resolver.rb +21 -0
- data/lib/lcp_ruby/workflow/transition_result.rb +20 -0
- data/lib/lcp_ruby/workflow/value_resolver.rb +58 -0
- data/lib/lcp_ruby/workflow/workflow_definition.rb +195 -0
- data/lib/lcp_ruby.rb +764 -0
- data/lib/rubocop/cop/lcp_ruby/no_hardcoded_i18n_string.rb +249 -0
- data/lib/tasks/lcp_ruby.rake +432 -0
- data/lib/tasks/lcp_ruby_assets.rake +37 -0
- data/lib/tasks/lcp_ruby_auth.rake +49 -0
- data/lib/tasks/lcp_ruby_db.rake +76 -0
- data/lib/tasks/lcp_ruby_doctor.rake +20 -0
- data/lib/tasks/lcp_ruby_feature_catalog.rake +61 -0
- data/lib/tasks/lcp_ruby_gapfree_sequences.rake +39 -0
- data/lib/tasks/lcp_ruby_i18n_check.rake +23 -0
- data/lib/tasks/lcp_ruby_i18n_lint.rake +20 -0
- data/lib/tasks/lcp_ruby_invariant_check.rake +72 -0
- data/vendor/assets/javascripts/lcp_ruby/activestorage.min.js +866 -0
- data/vendor/assets/javascripts/lcp_ruby/highlight.min.js +1244 -0
- data/vendor/assets/javascripts/lcp_ruby/lucide.min.js +12 -0
- data/vendor/assets/javascripts/lcp_ruby/stimulus.umd.js +2588 -0
- data/vendor/assets/javascripts/lcp_ruby/tom-select.complete.min.js +444 -0
- data/vendor/assets/stylesheets/lcp_ruby/highlight-github.min.css +12 -0
- data/vendor/assets/stylesheets/lcp_ruby/tom-select.css +412 -0
- metadata +1950 -0
|
@@ -0,0 +1,2324 @@
|
|
|
1
|
+
# Presenters Guide
|
|
2
|
+
|
|
3
|
+
Presenters define the UI layer of your LCP Ruby application: how records are listed, displayed, edited, searched, and what actions are available. A single model can have multiple presenters -- for example, one for full administration and another for a read-only pipeline view.
|
|
4
|
+
|
|
5
|
+
This guide walks through building presenters step-by-step, from a minimal setup to advanced features. Every example is shown in both YAML and Ruby DSL.
|
|
6
|
+
|
|
7
|
+
For the full attribute reference, see the [Presenters Reference](../reference/presenters.md) and the [Presenter DSL Reference](../reference/presenter-dsl.md).
|
|
8
|
+
|
|
9
|
+
## File Locations
|
|
10
|
+
|
|
11
|
+
Presenter files live in `config/lcp_ruby/presenters/`:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
config/lcp_ruby/presenters/
|
|
15
|
+
todo_list.yml # YAML format
|
|
16
|
+
deal.rb # Ruby DSL format
|
|
17
|
+
contact.yml
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Both formats produce the same internal representation. A project can mix `.yml` and `.rb` files, but each presenter name must be unique across all files.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Your First Presenter
|
|
25
|
+
|
|
26
|
+
A minimal presenter needs a `name`, a `model`, a `slug` (for URL routing), and at least an index configuration to list records.
|
|
27
|
+
|
|
28
|
+
**YAML:**
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
# config/lcp_ruby/presenters/todo_list.yml
|
|
32
|
+
presenter:
|
|
33
|
+
name: todo_list
|
|
34
|
+
model: todo_list
|
|
35
|
+
label: "Todo Lists"
|
|
36
|
+
slug: todo-lists
|
|
37
|
+
icon: check-square
|
|
38
|
+
|
|
39
|
+
index:
|
|
40
|
+
table_columns:
|
|
41
|
+
- { field: name, link_to: show, sortable: true }
|
|
42
|
+
- { field: created_at, renderer: relative_date }
|
|
43
|
+
|
|
44
|
+
show:
|
|
45
|
+
layout:
|
|
46
|
+
- section: "Details"
|
|
47
|
+
fields:
|
|
48
|
+
- { field: name, renderer: heading }
|
|
49
|
+
- { field: created_at, renderer: datetime }
|
|
50
|
+
|
|
51
|
+
form:
|
|
52
|
+
sections:
|
|
53
|
+
- title: "Todo List"
|
|
54
|
+
fields:
|
|
55
|
+
- { field: name, placeholder: "List name...", autofocus: true }
|
|
56
|
+
|
|
57
|
+
actions:
|
|
58
|
+
collection:
|
|
59
|
+
- { name: create, type: built_in, label: "New List", icon: plus }
|
|
60
|
+
single:
|
|
61
|
+
- { name: show, type: built_in, icon: eye }
|
|
62
|
+
- { name: edit, type: built_in, icon: pencil }
|
|
63
|
+
- { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Ruby DSL:**
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# config/lcp_ruby/presenters/todo_list.rb
|
|
70
|
+
define_presenter :todo_list do
|
|
71
|
+
model :todo_list
|
|
72
|
+
label "Todo Lists"
|
|
73
|
+
slug "todo-lists"
|
|
74
|
+
icon "check-square"
|
|
75
|
+
|
|
76
|
+
index do
|
|
77
|
+
column :name, link_to: :show, sortable: true
|
|
78
|
+
column :created_at, renderer: :relative_date
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
show do
|
|
82
|
+
section "Details" do
|
|
83
|
+
field :name, renderer: :heading
|
|
84
|
+
field :created_at, renderer: :datetime
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
form do
|
|
89
|
+
section "Todo List" do
|
|
90
|
+
field :name, placeholder: "List name...", autofocus: true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
action :create, type: :built_in, on: :collection, label: "New List", icon: "plus"
|
|
95
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
96
|
+
action :edit, type: :built_in, on: :single, icon: "pencil"
|
|
97
|
+
action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With this in place, navigating to `/todo-lists` renders a paginated table. Each record has show, edit, and destroy buttons.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Index Configuration
|
|
106
|
+
|
|
107
|
+
The index controls the record list view: which columns appear, how they sort, pagination, and visual formatting.
|
|
108
|
+
|
|
109
|
+
### Columns, Sorting, and Pagination
|
|
110
|
+
|
|
111
|
+
**YAML:**
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
index:
|
|
115
|
+
default_sort: { field: created_at, direction: desc }
|
|
116
|
+
per_page: 20
|
|
117
|
+
row_click: show
|
|
118
|
+
table_columns:
|
|
119
|
+
- field: name
|
|
120
|
+
width: "30%"
|
|
121
|
+
link_to: show
|
|
122
|
+
sortable: true
|
|
123
|
+
- field: email
|
|
124
|
+
width: "25%"
|
|
125
|
+
renderer: email_link
|
|
126
|
+
sortable: true
|
|
127
|
+
- field: status
|
|
128
|
+
width: "15%"
|
|
129
|
+
renderer: badge
|
|
130
|
+
options:
|
|
131
|
+
color_map:
|
|
132
|
+
active: green
|
|
133
|
+
inactive: gray
|
|
134
|
+
sortable: true
|
|
135
|
+
- field: revenue
|
|
136
|
+
width: "15%"
|
|
137
|
+
renderer: currency
|
|
138
|
+
options:
|
|
139
|
+
currency: "$"
|
|
140
|
+
precision: 2
|
|
141
|
+
sortable: true
|
|
142
|
+
summary: sum
|
|
143
|
+
- { field: updated_at, renderer: relative_date }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Ruby DSL:**
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
index do
|
|
150
|
+
default_sort :created_at, :desc
|
|
151
|
+
per_page 20
|
|
152
|
+
row_click :show
|
|
153
|
+
|
|
154
|
+
column :name, width: "30%", link_to: :show, sortable: true
|
|
155
|
+
column :email, width: "25%", renderer: :email_link, sortable: true
|
|
156
|
+
column :status, width: "15%", renderer: :badge, sortable: true,
|
|
157
|
+
options: { color_map: { active: "green", inactive: "gray" } }
|
|
158
|
+
column :revenue, width: "15%", renderer: :currency, sortable: true, summary: :sum,
|
|
159
|
+
options: { currency: "$", precision: 2 }
|
|
160
|
+
column :updated_at, renderer: :relative_date
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Key options:
|
|
165
|
+
|
|
166
|
+
| Option | Description |
|
|
167
|
+
|--------|-------------|
|
|
168
|
+
| `default_sort` | Default column and direction for ordering |
|
|
169
|
+
| `per_page` | Records per page (Kaminari pagination) |
|
|
170
|
+
| `row_click: show` | Makes the entire table row clickable |
|
|
171
|
+
| `link_to: show` | **Legacy.** Makes a specific column's cell a link to the current row's show page. Preserved for backward compatibility; prefer `link: true` for new configurations |
|
|
172
|
+
| `link: true` | Wrap the cell in a link. On dot-path or plain `belongs_to` columns links to the associated record; on scalar columns links to the current row. See [Linking dot-path fields and columns](#linking-dot-path-fields-and-columns) |
|
|
173
|
+
| `link_through: :x` | Follow `belongs_to :x` on the current model for the link target. Used when the displayed field's name doesn't correspond to the linked entity |
|
|
174
|
+
| `sortable` | Enables column-header sort toggle |
|
|
175
|
+
| `renderer` | Visual renderer for the cell value (see [Renderers Guide](display-types.md)) |
|
|
176
|
+
| `summary` | Adds a footer row with `sum`, `avg`, or `count` |
|
|
177
|
+
|
|
178
|
+
### Aggregate Columns
|
|
179
|
+
|
|
180
|
+
Columns can reference [aggregate fields](../reference/models.md#aggregates) defined on the model. Aggregate columns are computed via SQL subqueries at query time — no database column is needed. They support sorting and renderers just like regular columns.
|
|
181
|
+
|
|
182
|
+
**YAML:**
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
index:
|
|
186
|
+
table_columns:
|
|
187
|
+
- { field: name, sortable: true }
|
|
188
|
+
- { field: contacts_count, sortable: true }
|
|
189
|
+
- { field: total_deal_value, renderer: currency, sortable: true }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Ruby DSL:**
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
index do
|
|
196
|
+
column :name, sortable: true
|
|
197
|
+
column :contacts_count, sortable: true
|
|
198
|
+
column :total_deal_value, renderer: :currency, sortable: true
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Aggregates are also usable in show page sections:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
show do
|
|
206
|
+
section "Statistics", columns: 2 do
|
|
207
|
+
field :contacts_count
|
|
208
|
+
field :total_deal_value, renderer: :currency
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Aggregate columns are visible to all roles regardless of field-level permissions. Only aggregates referenced in the current presenter's columns are included in the query — unreferenced aggregates add zero overhead.
|
|
214
|
+
|
|
215
|
+
### Linking dot-path fields and columns
|
|
216
|
+
|
|
217
|
+
A dot-path column like `"manager.display_name"` renders the manager's name but by default the cell is plain text. Add `link: true` to wrap the value in a link to the manager's own show page. The same options work on show page fields.
|
|
218
|
+
|
|
219
|
+
**Auto-detection.** `link: true` resolves the target from the displayed value:
|
|
220
|
+
|
|
221
|
+
- **Dot-path** (`"manager.display_name"`) — links to the manager record.
|
|
222
|
+
- **Multi-level dot-path** (`"manager.organizational_unit.name"`) — follows the full chain and links to the org unit (where the displayed value lives).
|
|
223
|
+
- **Terminal `belongs_to`** (`"manager.organizational_unit"`) — links to that org unit; the displayed value defaults to the associated record's `to_label` / `to_s`.
|
|
224
|
+
- **Plain `belongs_to` field** (`field :manager`) — links to the manager record.
|
|
225
|
+
|
|
226
|
+
Use `link_through: :x` when the field name doesn't match the link target (e.g. a precomputed virtual column).
|
|
227
|
+
|
|
228
|
+
**YAML:**
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
index:
|
|
232
|
+
table_columns:
|
|
233
|
+
- { field: display_name, link_to: show } # legacy, unchanged
|
|
234
|
+
- { field: "manager.display_name", label: "Manager", link: true }
|
|
235
|
+
- { field: "organizational_unit.name", label: "Org Unit", link: true }
|
|
236
|
+
- { field: manager_label, link_through: manager } # virtual column
|
|
237
|
+
|
|
238
|
+
show:
|
|
239
|
+
layout:
|
|
240
|
+
- section: "Work Details"
|
|
241
|
+
columns: 2
|
|
242
|
+
fields:
|
|
243
|
+
- { field: "manager.display_name", label: "Manager", link: true }
|
|
244
|
+
- { field: "organizational_unit.name", label: "Org Unit", link: true }
|
|
245
|
+
- { field: position, link: true } # plain belongs_to
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Ruby DSL:**
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
index do
|
|
252
|
+
column :display_name, link_to: :show
|
|
253
|
+
column "manager.display_name", label: "Manager", link: true
|
|
254
|
+
column "organizational_unit.name", label: "Org Unit", link: true
|
|
255
|
+
column :manager_label, link_through: :manager
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
show do
|
|
259
|
+
section "Work Details", columns: 2 do
|
|
260
|
+
field "manager.display_name", label: "Manager", link: true
|
|
261
|
+
field "organizational_unit.name", label: "Org Unit", link: true
|
|
262
|
+
field :position, link: true
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Safe-by-default.** A link renders only when the target record exists, is not soft-deleted, and the current user has `:show` on the target model; otherwise the value renders as plain text. A blank displayed value (the empty-value placeholder "—") is never wrapped.
|
|
268
|
+
|
|
269
|
+
**Legacy `link_to: :show`.** On columns, `link_to: :show` always links to the **current row** regardless of the column field. It is preserved unchanged for backward compatibility — new configurations should prefer `link: true`, which auto-detects the target. Combining `link_to: :show` with `link_through:` is a boot error.
|
|
270
|
+
|
|
271
|
+
**Grouped queries.** Grouped-query presenters reject `link:` / `link_through:` / `link_to:` on columns — their rows are aggregate wrappers, not records.
|
|
272
|
+
|
|
273
|
+
**Eager loading.** Dot-path fields already trigger eager loading of their associations. Adding `link: true` on a plain `belongs_to` field (or using `link_through:` on a non-dot-path field) automatically includes the referenced association — no manual `includes:` needed. See [Eager Loading Reference](../reference/eager-loading.md) for the full auto-detection table.
|
|
274
|
+
|
|
275
|
+
### Empty State and Actions Position
|
|
276
|
+
|
|
277
|
+
**YAML:**
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
index:
|
|
281
|
+
empty_message: "No contacts found. Create your first contact to get started."
|
|
282
|
+
actions_position: dropdown
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Ruby DSL:**
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
index do
|
|
289
|
+
empty_message "No contacts found. Create your first contact to get started."
|
|
290
|
+
actions_position :dropdown
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`actions_position: dropdown` groups all single-record actions into a dropdown menu instead of rendering them as inline buttons. This is useful when you have many actions per row.
|
|
295
|
+
|
|
296
|
+
### Responsive Columns and Pinning
|
|
297
|
+
|
|
298
|
+
Hide columns on small screens and pin important columns to stay visible during horizontal scroll.
|
|
299
|
+
|
|
300
|
+
**YAML:**
|
|
301
|
+
|
|
302
|
+
```yaml
|
|
303
|
+
table_columns:
|
|
304
|
+
- field: name
|
|
305
|
+
pinned: left
|
|
306
|
+
sortable: true
|
|
307
|
+
- field: email
|
|
308
|
+
hidden_on: [mobile, tablet]
|
|
309
|
+
- field: phone
|
|
310
|
+
hidden_on: mobile
|
|
311
|
+
- field: status
|
|
312
|
+
renderer: badge
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Ruby DSL:**
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
index do
|
|
319
|
+
column :name, pinned: :left, sortable: true
|
|
320
|
+
column :email, hidden_on: [:mobile, :tablet]
|
|
321
|
+
column :phone, hidden_on: :mobile
|
|
322
|
+
column :status, renderer: :badge
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Conditional Row Styling (`item_classes`)
|
|
327
|
+
|
|
328
|
+
Apply CSS classes to table rows, tile cards, or tree nodes based on record field values. This provides immediate visual cues — red rows for overdue items, strikethrough for completed records, bold for high priority.
|
|
329
|
+
|
|
330
|
+
**YAML:**
|
|
331
|
+
|
|
332
|
+
```yaml
|
|
333
|
+
index:
|
|
334
|
+
table_columns:
|
|
335
|
+
- { field: title, link_to: show }
|
|
336
|
+
- { field: status, renderer: badge }
|
|
337
|
+
- { field: priority }
|
|
338
|
+
item_classes:
|
|
339
|
+
- class: "lcp-row-muted lcp-row-strikethrough"
|
|
340
|
+
when: { field: status, operator: eq, value: "done" }
|
|
341
|
+
- class: "lcp-row-danger"
|
|
342
|
+
when: { field: status, operator: eq, value: "overdue" }
|
|
343
|
+
- class: "lcp-row-bold"
|
|
344
|
+
when: { field: priority, operator: eq, value: "critical" }
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**DSL:**
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
index do
|
|
351
|
+
column :title, link_to: :show
|
|
352
|
+
column :status, renderer: :badge
|
|
353
|
+
column :priority
|
|
354
|
+
|
|
355
|
+
item_class "lcp-row-muted lcp-row-strikethrough",
|
|
356
|
+
when: { field: :status, operator: :eq, value: "done" }
|
|
357
|
+
item_class "lcp-row-danger",
|
|
358
|
+
when: { field: :status, operator: :eq, value: "overdue" }
|
|
359
|
+
item_class "lcp-row-bold",
|
|
360
|
+
when: { field: :priority, operator: :eq, value: "critical" }
|
|
361
|
+
end
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
All matching rules accumulate — a record can be both bold and red at the same time. Rules work across all index layouts (table, tiles, tree).
|
|
365
|
+
|
|
366
|
+
Built-in utility classes: `lcp-row-danger`, `lcp-row-warning`, `lcp-row-success`, `lcp-row-info`, `lcp-row-muted`, `lcp-row-bold`, `lcp-row-strikethrough`. All background classes use CSS custom properties for theming. Custom CSS classes are also supported.
|
|
367
|
+
|
|
368
|
+
For complex conditions that require server-side logic, use service conditions:
|
|
369
|
+
|
|
370
|
+
```yaml
|
|
371
|
+
item_classes:
|
|
372
|
+
- class: "lcp-row-danger"
|
|
373
|
+
when: { service: overdue_checker }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
See [Presenters Reference — item_classes](../reference/presenters.md#item_classes) for the full attribute reference.
|
|
377
|
+
|
|
378
|
+
### Pagination Footer
|
|
379
|
+
|
|
380
|
+
The pagination footer is a horizontal bar below the index content with up to three elements: a per-page size selector (left), a record count (center), and Kaminari page links (right).
|
|
381
|
+
|
|
382
|
+
**YAML:**
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
index:
|
|
386
|
+
per_page: 25
|
|
387
|
+
per_page_options: [10, 25, 50, 100]
|
|
388
|
+
show_record_count: true # default: true
|
|
389
|
+
pagination_ends: false # default: false (from global config)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Ruby DSL:**
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
index do
|
|
396
|
+
per_page 25
|
|
397
|
+
per_page_options 10, 25, 50, 100
|
|
398
|
+
show_record_count true
|
|
399
|
+
pagination_ends false
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Key options:
|
|
404
|
+
|
|
405
|
+
| Option | Description |
|
|
406
|
+
|--------|-------------|
|
|
407
|
+
| `per_page_options` | Array of page sizes for the dropdown selector. When absent, no dropdown renders. |
|
|
408
|
+
| `show_record_count` | Shows "Showing 1-25 of 312 records" text. Default `true`. Set to `false` to hide. |
|
|
409
|
+
| `pagination_ends` | Shows "First"/"Last" page links. Default from global [`LcpRuby.configuration.pagination_ends`](../reference/engine-configuration.md#pagination_ends). |
|
|
410
|
+
|
|
411
|
+
Behavior per layout:
|
|
412
|
+
|
|
413
|
+
| Layout | Per-page selector | Record count | Page links |
|
|
414
|
+
|--------|-------------------|--------------|------------|
|
|
415
|
+
| table | yes (if configured) | "records" | yes |
|
|
416
|
+
| tiles | yes (if configured) | "records" | yes |
|
|
417
|
+
| grouped | yes (if configured) | "groups" | yes |
|
|
418
|
+
| tree | no | total only (no range) | no |
|
|
419
|
+
|
|
420
|
+
When all records fit on one page the text simplifies to e.g. "12 records" (no "Showing X-Y" prefix). Empty result sets render no footer at all.
|
|
421
|
+
|
|
422
|
+
See [Presenters Reference — show_record_count](../reference/presenters.md#show_record_count) and [pagination_ends](../reference/presenters.md#pagination_ends) for full attribute details.
|
|
423
|
+
|
|
424
|
+
### Reorderable Index (Record Positioning)
|
|
425
|
+
|
|
426
|
+
For models with [`positioning`](../reference/models.md#positioning), you can enable drag-and-drop reordering:
|
|
427
|
+
|
|
428
|
+
**YAML:**
|
|
429
|
+
|
|
430
|
+
```yaml
|
|
431
|
+
index:
|
|
432
|
+
reorderable: true
|
|
433
|
+
table_columns:
|
|
434
|
+
- { field: name, link_to: show }
|
|
435
|
+
- { field: position, sortable: true }
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**DSL:**
|
|
439
|
+
|
|
440
|
+
```ruby
|
|
441
|
+
index do
|
|
442
|
+
reorderable true
|
|
443
|
+
column :name, link_to: :show
|
|
444
|
+
column :position, sortable: true
|
|
445
|
+
end
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
When `reorderable: true`:
|
|
449
|
+
- Drag handles appear as the first column
|
|
450
|
+
- Records are sorted by position by default (no need for explicit `default_sort`)
|
|
451
|
+
- The frontend sends `PATCH /:slug/:id/reorder` requests with relative positioning (`{ after: id }` or `{ before: id }`)
|
|
452
|
+
- Handles are automatically disabled when a search query is active or when sorting by a non-position column
|
|
453
|
+
- Reordering requires `update` CRUD permission and the position field in writable fields
|
|
454
|
+
|
|
455
|
+
The position column is optional in `table_columns` — drag-and-drop works regardless. Include it when users want to see the numeric order.
|
|
456
|
+
|
|
457
|
+
See [Record Positioning](../design/record_positioning.md) for the full design, including scoped positioning, concurrent edit detection, and permission patterns.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Show Configuration
|
|
462
|
+
|
|
463
|
+
The show view displays a single record's details organized into sections.
|
|
464
|
+
|
|
465
|
+
### Sections and Fields
|
|
466
|
+
|
|
467
|
+
**YAML:**
|
|
468
|
+
|
|
469
|
+
```yaml
|
|
470
|
+
show:
|
|
471
|
+
layout:
|
|
472
|
+
- section: "Contact Details"
|
|
473
|
+
columns: 2
|
|
474
|
+
fields:
|
|
475
|
+
- { field: name, renderer: heading, col_span: 2 }
|
|
476
|
+
- { field: email, renderer: email_link }
|
|
477
|
+
- { field: phone, renderer: phone_link }
|
|
478
|
+
- field: status
|
|
479
|
+
renderer: badge
|
|
480
|
+
options:
|
|
481
|
+
color_map:
|
|
482
|
+
active: green
|
|
483
|
+
inactive: gray
|
|
484
|
+
- section: "Notes"
|
|
485
|
+
fields:
|
|
486
|
+
- { field: notes, renderer: rich_text }
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Ruby DSL:**
|
|
490
|
+
|
|
491
|
+
```ruby
|
|
492
|
+
show do
|
|
493
|
+
section "Contact Details", columns: 2 do
|
|
494
|
+
field :name, renderer: :heading, col_span: 2
|
|
495
|
+
field :email, renderer: :email_link
|
|
496
|
+
field :phone, renderer: :phone_link
|
|
497
|
+
field :status, renderer: :badge,
|
|
498
|
+
options: { color_map: { active: "green", inactive: "gray" } }
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
section "Notes" do
|
|
502
|
+
field :notes, renderer: :rich_text
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Use `columns` to control the grid layout and `col_span` to make a field stretch across multiple columns.
|
|
508
|
+
|
|
509
|
+
### Responsive Sections
|
|
510
|
+
|
|
511
|
+
Override the column count at different breakpoints so the layout adapts to smaller screens.
|
|
512
|
+
|
|
513
|
+
**YAML:**
|
|
514
|
+
|
|
515
|
+
```yaml
|
|
516
|
+
show:
|
|
517
|
+
layout:
|
|
518
|
+
- section: "Deal Overview"
|
|
519
|
+
columns: 3
|
|
520
|
+
responsive:
|
|
521
|
+
tablet:
|
|
522
|
+
columns: 2
|
|
523
|
+
mobile:
|
|
524
|
+
columns: 1
|
|
525
|
+
fields:
|
|
526
|
+
- { field: title, renderer: heading, col_span: 3 }
|
|
527
|
+
- { field: stage, renderer: badge }
|
|
528
|
+
- { field: value, renderer: currency }
|
|
529
|
+
- { field: close_date, renderer: date }
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Ruby DSL:**
|
|
533
|
+
|
|
534
|
+
```ruby
|
|
535
|
+
show do
|
|
536
|
+
section "Deal Overview", columns: 3, responsive: { tablet: 2, mobile: 1 } do
|
|
537
|
+
field :title, renderer: :heading, col_span: 3
|
|
538
|
+
field :stage, renderer: :badge
|
|
539
|
+
field :value, renderer: :currency
|
|
540
|
+
field :close_date, renderer: :date
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Association Lists
|
|
546
|
+
|
|
547
|
+
Display related records as a list within the show page using `type: association_list`. Records can render with rich display templates (title, subtitle, icon, badge), links, sorting, and limits.
|
|
548
|
+
|
|
549
|
+
**YAML:**
|
|
550
|
+
|
|
551
|
+
```yaml
|
|
552
|
+
show:
|
|
553
|
+
layout:
|
|
554
|
+
- section: "Company Info"
|
|
555
|
+
fields:
|
|
556
|
+
- { field: name, renderer: heading }
|
|
557
|
+
- section: "Contacts"
|
|
558
|
+
type: association_list
|
|
559
|
+
association: contacts
|
|
560
|
+
display_template: default # Uses display template from contact model
|
|
561
|
+
link: true # Wrap each record in a link to its show page
|
|
562
|
+
sort: { last_name: asc }
|
|
563
|
+
limit: 10
|
|
564
|
+
empty_message: "No contacts yet."
|
|
565
|
+
- section: "Deals"
|
|
566
|
+
type: association_list
|
|
567
|
+
association: deals
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Ruby DSL:**
|
|
571
|
+
|
|
572
|
+
```ruby
|
|
573
|
+
show do
|
|
574
|
+
section "Company Info" do
|
|
575
|
+
field :name, renderer: :heading
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
association_list "Contacts", association: :contacts,
|
|
579
|
+
display_template: :default, link: true,
|
|
580
|
+
sort: { last_name: :asc }, limit: 10,
|
|
581
|
+
empty_message: "No contacts yet."
|
|
582
|
+
association_list "Deals", association: :deals
|
|
583
|
+
end
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The association must be defined as `has_many` in the model.
|
|
587
|
+
|
|
588
|
+
#### Linking Through Join Models
|
|
589
|
+
|
|
590
|
+
When displaying a join/bridge model (e.g., group memberships) but the user expects to click through to the related entity (e.g., the employee), use `link_through`:
|
|
591
|
+
|
|
592
|
+
**YAML:**
|
|
593
|
+
|
|
594
|
+
```yaml
|
|
595
|
+
- section: "Members"
|
|
596
|
+
type: association_list
|
|
597
|
+
association: group_memberships
|
|
598
|
+
link_through: employee # Links to employee, not group_membership
|
|
599
|
+
display_template: default
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Ruby DSL:**
|
|
603
|
+
|
|
604
|
+
```ruby
|
|
605
|
+
association_list "Members", association: :group_memberships,
|
|
606
|
+
link_through: :employee, display_template: :default
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
The `link_through` value must be a non-polymorphic `belongs_to` association on the target model (group_membership). If the related record is nil or soft-deleted, the item renders without a link. The user must have `show` permission on the target model for links to appear.
|
|
610
|
+
|
|
611
|
+
**Display templates** are defined on the target model (see [Models Reference](../reference/models.md#display-templates)). For example, the contact model might define:
|
|
612
|
+
|
|
613
|
+
**YAML:**
|
|
614
|
+
|
|
615
|
+
```yaml
|
|
616
|
+
display_templates:
|
|
617
|
+
default:
|
|
618
|
+
template: "{first_name} {last_name}"
|
|
619
|
+
subtitle: "{position} at {company.name}"
|
|
620
|
+
icon: user
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Ruby DSL:**
|
|
624
|
+
|
|
625
|
+
```ruby
|
|
626
|
+
define_model :contact do
|
|
627
|
+
# ... fields, associations ...
|
|
628
|
+
|
|
629
|
+
display_template :default,
|
|
630
|
+
template: "{first_name} {last_name}",
|
|
631
|
+
subtitle: "{position} at {company.name}",
|
|
632
|
+
icon: "user"
|
|
633
|
+
end
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
This renders each contact with a structured layout showing the name, position, and company. Without a display template, records fall back to plain `to_label` text.
|
|
637
|
+
|
|
638
|
+
Options reference: `display`, `link`, `sort`, `limit`, `empty_message`, `scope` — see [Presenters Reference](../reference/presenters.md#association-list-sections).
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Form Configuration
|
|
643
|
+
|
|
644
|
+
Forms control how records are created and edited. You define one or more sections, each containing fields.
|
|
645
|
+
|
|
646
|
+
### Basic Form
|
|
647
|
+
|
|
648
|
+
**YAML:**
|
|
649
|
+
|
|
650
|
+
```yaml
|
|
651
|
+
form:
|
|
652
|
+
sections:
|
|
653
|
+
- title: "Contact Information"
|
|
654
|
+
columns: 2
|
|
655
|
+
fields:
|
|
656
|
+
- { field: first_name, placeholder: "First name...", autofocus: true }
|
|
657
|
+
- { field: last_name, placeholder: "Last name..." }
|
|
658
|
+
- { field: email, placeholder: "email@example.com" }
|
|
659
|
+
- { field: phone }
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Ruby DSL:**
|
|
663
|
+
|
|
664
|
+
```ruby
|
|
665
|
+
form do
|
|
666
|
+
section "Contact Information", columns: 2 do
|
|
667
|
+
field :first_name, placeholder: "First name...", autofocus: true
|
|
668
|
+
field :last_name, placeholder: "Last name..."
|
|
669
|
+
field :email, placeholder: "email@example.com"
|
|
670
|
+
field :phone
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Flat vs Tabs Layout
|
|
676
|
+
|
|
677
|
+
By default, sections render as stacked cards (`flat` layout). Set `layout: tabs` to render each section as a tab.
|
|
678
|
+
|
|
679
|
+
**YAML:**
|
|
680
|
+
|
|
681
|
+
```yaml
|
|
682
|
+
form:
|
|
683
|
+
layout: tabs
|
|
684
|
+
sections:
|
|
685
|
+
- title: "General"
|
|
686
|
+
fields:
|
|
687
|
+
- { field: title }
|
|
688
|
+
- { field: description, input_type: textarea }
|
|
689
|
+
- title: "Pricing"
|
|
690
|
+
fields:
|
|
691
|
+
- { field: price, input_type: number, prefix: "$" }
|
|
692
|
+
- { field: currency, input_type: select }
|
|
693
|
+
- title: "Advanced"
|
|
694
|
+
fields:
|
|
695
|
+
- { field: notes, input_type: rich_text_editor }
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**Ruby DSL:**
|
|
699
|
+
|
|
700
|
+
```ruby
|
|
701
|
+
form do
|
|
702
|
+
layout :tabs
|
|
703
|
+
|
|
704
|
+
section "General" do
|
|
705
|
+
field :title
|
|
706
|
+
field :description, input_type: :textarea
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
section "Pricing" do
|
|
710
|
+
field :price, input_type: :number, prefix: "$"
|
|
711
|
+
field :currency, input_type: :select
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
section "Advanced" do
|
|
715
|
+
field :notes, input_type: :rich_text_editor
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Input Types
|
|
721
|
+
|
|
722
|
+
LCP Ruby picks a default input type based on the field's model type, but you can override it.
|
|
723
|
+
|
|
724
|
+
| Input Type | Renders | Default For |
|
|
725
|
+
|------------|---------|-------------|
|
|
726
|
+
| `text` | Single-line text input | `string` fields |
|
|
727
|
+
| `textarea` | Multi-line text area | `text` fields |
|
|
728
|
+
| `select` | Dropdown (from `enum_values`) | `enum` fields |
|
|
729
|
+
| `number` | Numeric input | `integer`, `float`, `decimal` |
|
|
730
|
+
| `date` / `date_picker` | Date picker | `date` fields |
|
|
731
|
+
| `datetime` | Datetime picker | `datetime` fields |
|
|
732
|
+
| `boolean` | Checkbox | `boolean` fields |
|
|
733
|
+
| `association_select` | Dropdown from associated model | FK fields |
|
|
734
|
+
| `rich_text_editor` | Rich text editor | `rich_text` fields |
|
|
735
|
+
| `slider` | Range slider | - |
|
|
736
|
+
| `toggle` | Toggle switch | - |
|
|
737
|
+
| `rating` | Star rating input | - |
|
|
738
|
+
| `array_input` | Tag-style chip input | `array` fields |
|
|
739
|
+
|
|
740
|
+
Array fields (`type: array`) automatically use `array_input` in forms and `collection` renderer in display. Configure suggestions and limits via `input_options`:
|
|
741
|
+
|
|
742
|
+
```ruby
|
|
743
|
+
field :tags, input_type: :array_input, input_options: {
|
|
744
|
+
placeholder: "Add a tag...",
|
|
745
|
+
max: 10,
|
|
746
|
+
suggestions: %w[ruby rails javascript python]
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Field Options
|
|
751
|
+
|
|
752
|
+
Fields support prefix, suffix, hints, placeholders, and input-specific options.
|
|
753
|
+
|
|
754
|
+
**YAML:**
|
|
755
|
+
|
|
756
|
+
```yaml
|
|
757
|
+
fields:
|
|
758
|
+
- field: title
|
|
759
|
+
placeholder: "Enter title..."
|
|
760
|
+
autofocus: true
|
|
761
|
+
col_span: 2
|
|
762
|
+
hint: "A short, descriptive name"
|
|
763
|
+
- field: price
|
|
764
|
+
input_type: number
|
|
765
|
+
prefix: "$"
|
|
766
|
+
suffix: "USD"
|
|
767
|
+
input_options:
|
|
768
|
+
min: 0
|
|
769
|
+
step: 0.01
|
|
770
|
+
- field: description
|
|
771
|
+
input_type: textarea
|
|
772
|
+
input_options:
|
|
773
|
+
rows: 6
|
|
774
|
+
max_length: 500
|
|
775
|
+
show_counter: true
|
|
776
|
+
- field: priority
|
|
777
|
+
input_type: slider
|
|
778
|
+
input_options:
|
|
779
|
+
min: 1
|
|
780
|
+
max: 10
|
|
781
|
+
step: 1
|
|
782
|
+
show_value: true
|
|
783
|
+
- field: internal_code
|
|
784
|
+
readonly: true
|
|
785
|
+
hint: "Auto-generated, cannot be changed"
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**Ruby DSL:**
|
|
789
|
+
|
|
790
|
+
```ruby
|
|
791
|
+
form do
|
|
792
|
+
section "Details", columns: 2 do
|
|
793
|
+
field :title, placeholder: "Enter title...", autofocus: true,
|
|
794
|
+
col_span: 2, hint: "A short, descriptive name"
|
|
795
|
+
field :price, input_type: :number, prefix: "$", suffix: "USD",
|
|
796
|
+
input_options: { min: 0, step: 0.01 }
|
|
797
|
+
field :description, input_type: :textarea,
|
|
798
|
+
input_options: { rows: 6, max_length: 500, show_counter: true }
|
|
799
|
+
field :priority, input_type: :slider,
|
|
800
|
+
input_options: { min: 1, max: 10, step: 1, show_value: true }
|
|
801
|
+
field :internal_code, readonly: true, hint: "Auto-generated, cannot be changed"
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Dynamic Defaults
|
|
807
|
+
|
|
808
|
+
Set default values that are resolved at form render time.
|
|
809
|
+
|
|
810
|
+
**YAML:**
|
|
811
|
+
|
|
812
|
+
```yaml
|
|
813
|
+
fields:
|
|
814
|
+
- { field: start_date, input_type: date, default: current_date }
|
|
815
|
+
- { field: assigned_to_id, input_type: association_select, default: current_user.id }
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
**Ruby DSL:**
|
|
819
|
+
|
|
820
|
+
```ruby
|
|
821
|
+
field :start_date, input_type: :date, default: "current_date"
|
|
822
|
+
field :assigned_to_id, input_type: :association_select, default: "current_user.id"
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
| Value | Description |
|
|
826
|
+
|-------|-------------|
|
|
827
|
+
| `current_date` | Today's date |
|
|
828
|
+
| `current_datetime` | Current date and time |
|
|
829
|
+
| `current_user.id` | The current user's ID |
|
|
830
|
+
|
|
831
|
+
### Dividers
|
|
832
|
+
|
|
833
|
+
Use dividers to visually separate groups of fields within a section.
|
|
834
|
+
|
|
835
|
+
**YAML:**
|
|
836
|
+
|
|
837
|
+
```yaml
|
|
838
|
+
fields:
|
|
839
|
+
- { field: first_name }
|
|
840
|
+
- { field: last_name }
|
|
841
|
+
- { type: divider, label: "Contact Information" }
|
|
842
|
+
- { field: email }
|
|
843
|
+
- { field: phone }
|
|
844
|
+
- { type: divider }
|
|
845
|
+
- { field: notes, input_type: textarea }
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
**Ruby DSL:**
|
|
849
|
+
|
|
850
|
+
```ruby
|
|
851
|
+
section "Contact Details", columns: 2 do
|
|
852
|
+
field :first_name
|
|
853
|
+
field :last_name
|
|
854
|
+
divider label: "Contact Information"
|
|
855
|
+
field :email
|
|
856
|
+
field :phone
|
|
857
|
+
divider
|
|
858
|
+
field :notes, input_type: :textarea
|
|
859
|
+
end
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
A divider with a `label` renders a labeled horizontal rule. Without a label, it renders a plain separator line.
|
|
863
|
+
|
|
864
|
+
### Association Selects
|
|
865
|
+
|
|
866
|
+
Foreign key fields can render as dropdowns populated from the associated model's records.
|
|
867
|
+
|
|
868
|
+
**YAML:**
|
|
869
|
+
|
|
870
|
+
```yaml
|
|
871
|
+
fields:
|
|
872
|
+
- { field: company_id, input_type: association_select }
|
|
873
|
+
- { field: contact_id, input_type: association_select }
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
**Ruby DSL:**
|
|
877
|
+
|
|
878
|
+
```ruby
|
|
879
|
+
field :company_id, input_type: :association_select
|
|
880
|
+
field :contact_id, input_type: :association_select
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
The dropdown display text uses `to_label` (if defined on the target model) or `to_s`. The target model must be registered in the LCP registry.
|
|
884
|
+
|
|
885
|
+
### Collapsible Sections
|
|
886
|
+
|
|
887
|
+
Sections can be collapsible, optionally starting collapsed. This is useful for "Advanced" or rarely-edited fields.
|
|
888
|
+
|
|
889
|
+
**YAML:**
|
|
890
|
+
|
|
891
|
+
```yaml
|
|
892
|
+
form:
|
|
893
|
+
sections:
|
|
894
|
+
- title: "Basic Information"
|
|
895
|
+
columns: 2
|
|
896
|
+
fields:
|
|
897
|
+
- { field: name }
|
|
898
|
+
- { field: email }
|
|
899
|
+
- title: "Advanced Options"
|
|
900
|
+
collapsible: true
|
|
901
|
+
collapsed: true
|
|
902
|
+
fields:
|
|
903
|
+
- { field: api_key }
|
|
904
|
+
- { field: webhook_url }
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Ruby DSL:**
|
|
908
|
+
|
|
909
|
+
```ruby
|
|
910
|
+
form do
|
|
911
|
+
section "Basic Information", columns: 2 do
|
|
912
|
+
field :name
|
|
913
|
+
field :email
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
section "Advanced Options", collapsible: true, collapsed: true do
|
|
917
|
+
field :api_key
|
|
918
|
+
field :webhook_url
|
|
919
|
+
end
|
|
920
|
+
end
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### Responsive Form Sections
|
|
924
|
+
|
|
925
|
+
Override the column count at different breakpoints.
|
|
926
|
+
|
|
927
|
+
**YAML:**
|
|
928
|
+
|
|
929
|
+
```yaml
|
|
930
|
+
form:
|
|
931
|
+
sections:
|
|
932
|
+
- title: "Contact Details"
|
|
933
|
+
columns: 3
|
|
934
|
+
responsive:
|
|
935
|
+
tablet:
|
|
936
|
+
columns: 2
|
|
937
|
+
mobile:
|
|
938
|
+
columns: 1
|
|
939
|
+
fields:
|
|
940
|
+
- { field: first_name }
|
|
941
|
+
- { field: last_name }
|
|
942
|
+
- { field: email }
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
**Ruby DSL:**
|
|
946
|
+
|
|
947
|
+
```ruby
|
|
948
|
+
form do
|
|
949
|
+
section "Contact Details", columns: 3, responsive: { tablet: 2, mobile: 1 } do
|
|
950
|
+
field :first_name
|
|
951
|
+
field :last_name
|
|
952
|
+
field :email
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
## Nested Fields
|
|
960
|
+
|
|
961
|
+
Use nested fields to manage a list of structured items inline within the parent form. Users can add, remove, and reorder items without leaving the page. There are three data sources:
|
|
962
|
+
|
|
963
|
+
### Association Source (has_many)
|
|
964
|
+
|
|
965
|
+
Edits child records from a `has_many` association:
|
|
966
|
+
|
|
967
|
+
**YAML:**
|
|
968
|
+
|
|
969
|
+
```yaml
|
|
970
|
+
form:
|
|
971
|
+
sections:
|
|
972
|
+
- title: "Line Items"
|
|
973
|
+
type: nested_fields
|
|
974
|
+
association: line_items
|
|
975
|
+
allow_add: true
|
|
976
|
+
allow_remove: true
|
|
977
|
+
add_label: "Add Line Item"
|
|
978
|
+
min: 1
|
|
979
|
+
max: 20
|
|
980
|
+
columns: 3
|
|
981
|
+
fields:
|
|
982
|
+
- { field: product_id, input_type: association_select }
|
|
983
|
+
- { field: quantity, input_type: number }
|
|
984
|
+
- { field: unit_price, input_type: number, prefix: "$" }
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
**Ruby DSL:**
|
|
988
|
+
|
|
989
|
+
```ruby
|
|
990
|
+
form do
|
|
991
|
+
nested_fields "Line Items", association: :line_items,
|
|
992
|
+
allow_add: true, allow_remove: true,
|
|
993
|
+
add_label: "Add Line Item", min: 1, max: 20, columns: 3 do
|
|
994
|
+
field :product_id, input_type: :association_select
|
|
995
|
+
field :quantity, input_type: :number
|
|
996
|
+
field :unit_price, input_type: :number, prefix: "$"
|
|
997
|
+
end
|
|
998
|
+
end
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
### JSON Field Source (Inline)
|
|
1002
|
+
|
|
1003
|
+
Stores items as a JSON array of hashes in a single column. Field types and labels are declared directly in the presenter — no model needed:
|
|
1004
|
+
|
|
1005
|
+
**YAML:**
|
|
1006
|
+
|
|
1007
|
+
```yaml
|
|
1008
|
+
form:
|
|
1009
|
+
sections:
|
|
1010
|
+
- title: "Workflow Steps"
|
|
1011
|
+
type: nested_fields
|
|
1012
|
+
json_field: steps
|
|
1013
|
+
allow_add: true
|
|
1014
|
+
allow_remove: true
|
|
1015
|
+
columns: 2
|
|
1016
|
+
fields:
|
|
1017
|
+
- { field: name, type: string, label: "Step Name" }
|
|
1018
|
+
- field: action_type
|
|
1019
|
+
type: string
|
|
1020
|
+
input_type: select
|
|
1021
|
+
options: [review, approve, notify]
|
|
1022
|
+
- field: timeout_days
|
|
1023
|
+
type: integer
|
|
1024
|
+
label: "Timeout (days)"
|
|
1025
|
+
visible_when: { field: action_type, operator: eq, value: review }
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**Ruby DSL:**
|
|
1029
|
+
|
|
1030
|
+
```ruby
|
|
1031
|
+
form do
|
|
1032
|
+
nested_fields "Workflow Steps", json_field: :steps,
|
|
1033
|
+
allow_add: true, allow_remove: true, columns: 2 do
|
|
1034
|
+
field :name, type: :string, label: "Step Name"
|
|
1035
|
+
field :action_type, type: :string, input_type: :select,
|
|
1036
|
+
input_options: { values: %w[review approve notify] }
|
|
1037
|
+
field :timeout_days, type: :integer, label: "Timeout (days)",
|
|
1038
|
+
visible_when: { field: :action_type, operator: :eq, value: "review" }
|
|
1039
|
+
end
|
|
1040
|
+
end
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
The parent model must have a `json` type field. Inline mode is best for simple structures where creating a model definition is overkill.
|
|
1044
|
+
|
|
1045
|
+
### JSON Field Source (Model-Backed)
|
|
1046
|
+
|
|
1047
|
+
For complex structures with validations, transforms, or custom types. Define a virtual model for the item structure:
|
|
1048
|
+
|
|
1049
|
+
```yaml
|
|
1050
|
+
# config/lcp_ruby/models/address.yml
|
|
1051
|
+
model:
|
|
1052
|
+
name: address
|
|
1053
|
+
table_name: _virtual # no DB table — metadata only
|
|
1054
|
+
fields:
|
|
1055
|
+
- name: street
|
|
1056
|
+
type: string
|
|
1057
|
+
validations: [{ type: presence }]
|
|
1058
|
+
- name: city
|
|
1059
|
+
type: string
|
|
1060
|
+
validations: [{ type: presence }]
|
|
1061
|
+
- name: zip
|
|
1062
|
+
type: string
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
Then reference it with `target_model:`:
|
|
1066
|
+
|
|
1067
|
+
**YAML:**
|
|
1068
|
+
|
|
1069
|
+
```yaml
|
|
1070
|
+
form:
|
|
1071
|
+
sections:
|
|
1072
|
+
- title: "Addresses"
|
|
1073
|
+
type: nested_fields
|
|
1074
|
+
json_field: addresses
|
|
1075
|
+
target_model: address
|
|
1076
|
+
allow_add: true
|
|
1077
|
+
allow_remove: true
|
|
1078
|
+
columns: 2
|
|
1079
|
+
fields:
|
|
1080
|
+
- { field: street }
|
|
1081
|
+
- { field: city }
|
|
1082
|
+
- { field: zip }
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
**Ruby DSL:**
|
|
1086
|
+
|
|
1087
|
+
```ruby
|
|
1088
|
+
form do
|
|
1089
|
+
nested_fields "Addresses", json_field: :addresses, target_model: :address,
|
|
1090
|
+
allow_add: true, allow_remove: true, columns: 2 do
|
|
1091
|
+
field :street
|
|
1092
|
+
field :city
|
|
1093
|
+
field :zip
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
Field metadata (type, label, validations) comes from the virtual model. Items are validated per-row on save. See [Virtual Models](../reference/models.md#virtual-models).
|
|
1099
|
+
|
|
1100
|
+
### Conditional Fields in Nested Rows
|
|
1101
|
+
|
|
1102
|
+
Fields inside nested rows support `visible_when` and `disable_when`, evaluated against the **current row's data**:
|
|
1103
|
+
|
|
1104
|
+
**YAML:**
|
|
1105
|
+
|
|
1106
|
+
```yaml
|
|
1107
|
+
- title: "Line Items"
|
|
1108
|
+
type: nested_fields
|
|
1109
|
+
association: line_items
|
|
1110
|
+
columns: 4
|
|
1111
|
+
fields:
|
|
1112
|
+
- { field: item_type, input_type: select }
|
|
1113
|
+
- { field: description }
|
|
1114
|
+
- field: discount_percent
|
|
1115
|
+
input_type: number
|
|
1116
|
+
visible_when: { field: item_type, operator: eq, value: discount }
|
|
1117
|
+
hint: "Enter discount percentage"
|
|
1118
|
+
- field: notes
|
|
1119
|
+
visible_when: { field: item_type, operator: in, value: "service,discount" }
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
**Ruby DSL:**
|
|
1123
|
+
|
|
1124
|
+
```ruby
|
|
1125
|
+
nested_fields "Line Items", association: :line_items, columns: 4 do
|
|
1126
|
+
field :item_type, input_type: :select
|
|
1127
|
+
field :description
|
|
1128
|
+
field :discount_percent, input_type: :number,
|
|
1129
|
+
visible_when: { field: :item_type, operator: :eq, value: "discount" },
|
|
1130
|
+
hint: "Enter discount percentage"
|
|
1131
|
+
field :notes,
|
|
1132
|
+
visible_when: { field: :item_type, operator: :in, value: "service,discount" }
|
|
1133
|
+
end
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
Each row evaluates conditions independently — changing `item_type` in one row does not affect other rows. See [Row-Scoped Conditions](conditional-rendering.md#row-scoped-conditions-in-nested-fields).
|
|
1137
|
+
|
|
1138
|
+
### Sub-Sections in Nested Rows
|
|
1139
|
+
|
|
1140
|
+
For complex items with many fields, use `section` blocks (in DSL) or `sub_sections` (in YAML) to group fields:
|
|
1141
|
+
|
|
1142
|
+
**YAML:**
|
|
1143
|
+
|
|
1144
|
+
```yaml
|
|
1145
|
+
- title: "Addresses"
|
|
1146
|
+
type: nested_fields
|
|
1147
|
+
json_field: addresses
|
|
1148
|
+
target_model: address
|
|
1149
|
+
sub_sections:
|
|
1150
|
+
- title: "Location"
|
|
1151
|
+
columns: 2
|
|
1152
|
+
fields:
|
|
1153
|
+
- { field: street }
|
|
1154
|
+
- { field: city }
|
|
1155
|
+
- title: "Additional"
|
|
1156
|
+
collapsible: true
|
|
1157
|
+
collapsed: true
|
|
1158
|
+
fields:
|
|
1159
|
+
- { field: notes }
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**Ruby DSL:**
|
|
1163
|
+
|
|
1164
|
+
```ruby
|
|
1165
|
+
nested_fields "Addresses", json_field: :addresses, target_model: :address do
|
|
1166
|
+
section "Location", columns: 2 do
|
|
1167
|
+
field :street
|
|
1168
|
+
field :city
|
|
1169
|
+
end
|
|
1170
|
+
section "Additional", collapsible: true, collapsed: true do
|
|
1171
|
+
field :notes
|
|
1172
|
+
end
|
|
1173
|
+
end
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
You cannot mix `field` and `section` calls in the same `nested_fields` block — use one or the other.
|
|
1177
|
+
|
|
1178
|
+
### Nested Fields Options
|
|
1179
|
+
|
|
1180
|
+
| Option | Default | Description |
|
|
1181
|
+
|--------|---------|-------------|
|
|
1182
|
+
| `association` | - | has_many association name (mutually exclusive with `json_field`) |
|
|
1183
|
+
| `json_field` | - | JSON column name (mutually exclusive with `association`) |
|
|
1184
|
+
| `target_model` | - | Virtual model for item structure (only with `json_field`) |
|
|
1185
|
+
| `allow_add` | `true` | Show a button to add new items |
|
|
1186
|
+
| `allow_remove` | `true` | Show a remove button on each row |
|
|
1187
|
+
| `add_label` | `"Add"` | Label for the add button |
|
|
1188
|
+
| `min` | - | Minimum number of items |
|
|
1189
|
+
| `max` | - | Maximum number of items |
|
|
1190
|
+
| `columns` | - | Grid columns for each row's field layout |
|
|
1191
|
+
| `empty_message` | - | Message when there are no items |
|
|
1192
|
+
| `sortable` | `false` | Enable drag-and-drop reordering |
|
|
1193
|
+
|
|
1194
|
+
### Sortable Nested Forms
|
|
1195
|
+
|
|
1196
|
+
Enable drag-and-drop reordering by setting `sortable: true`. The position field is automatically hidden from the visible form and managed via hidden inputs.
|
|
1197
|
+
|
|
1198
|
+
**YAML:**
|
|
1199
|
+
|
|
1200
|
+
```yaml
|
|
1201
|
+
- title: "Checklist Items"
|
|
1202
|
+
type: nested_fields
|
|
1203
|
+
association: checklist_items
|
|
1204
|
+
sortable: true
|
|
1205
|
+
add_label: "Add Item"
|
|
1206
|
+
fields:
|
|
1207
|
+
- { field: title }
|
|
1208
|
+
- { field: completed, input_type: boolean }
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
**Ruby DSL:**
|
|
1212
|
+
|
|
1213
|
+
```ruby
|
|
1214
|
+
nested_fields "Checklist Items", association: :checklist_items,
|
|
1215
|
+
sortable: true, add_label: "Add Item" do
|
|
1216
|
+
field :title
|
|
1217
|
+
field :completed, input_type: :boolean
|
|
1218
|
+
end
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
The child model should have an integer `position` field, and the parent association should specify `order: { position: asc }`. For a custom position field name, pass a string instead of `true`: `sortable: "sort_order"`. For JSON field items, array order is the position — no position key needed.
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
1225
|
+
## Search and Filters
|
|
1226
|
+
|
|
1227
|
+
The search configuration adds a search bar and optional predefined filter buttons to the index page.
|
|
1228
|
+
|
|
1229
|
+
> **Looking for dropdown/selectbox filters?** Use [Search Parameters](#search-parameters) below to add always-visible select controls (e.g., "Department" or "Status" dropdowns) directly in the toolbar. For filters that apply across **all zones** on a composite page, use [Page Parameters](../reference/pages.md#page-parameters) instead.
|
|
1230
|
+
|
|
1231
|
+
**YAML:**
|
|
1232
|
+
|
|
1233
|
+
```yaml
|
|
1234
|
+
search:
|
|
1235
|
+
enabled: true
|
|
1236
|
+
searchable_fields: [title, description, email]
|
|
1237
|
+
placeholder: "Search contacts..."
|
|
1238
|
+
predefined_filters:
|
|
1239
|
+
- { name: all, label: "All", default: true }
|
|
1240
|
+
- { name: active, label: "Active", scope: active }
|
|
1241
|
+
- { name: vip, label: "VIP Clients", scope: vip_clients }
|
|
1242
|
+
- { name: recent, label: "Recent", scope: recent }
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
**Ruby DSL:**
|
|
1246
|
+
|
|
1247
|
+
```ruby
|
|
1248
|
+
search do
|
|
1249
|
+
searchable_fields :title, :description, :email
|
|
1250
|
+
placeholder "Search contacts..."
|
|
1251
|
+
filter :all, label: "All", default: true
|
|
1252
|
+
filter :active, label: "Active", scope: :active
|
|
1253
|
+
filter :vip, label: "VIP Clients", scope: :vip_clients
|
|
1254
|
+
filter :recent, label: "Recent", scope: :recent
|
|
1255
|
+
end
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
Each filter (except the default "all") maps to a named scope defined in the model YAML. The `all` filter shows unfiltered results.
|
|
1259
|
+
|
|
1260
|
+
To disable search entirely:
|
|
1261
|
+
|
|
1262
|
+
**YAML:**
|
|
1263
|
+
|
|
1264
|
+
```yaml
|
|
1265
|
+
search:
|
|
1266
|
+
enabled: false
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
**Ruby DSL:**
|
|
1270
|
+
|
|
1271
|
+
```ruby
|
|
1272
|
+
search enabled: false
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
### Type-Aware Quick Search
|
|
1276
|
+
|
|
1277
|
+
The quick search bar (`?qs=` parameter) is type-aware. Each `searchable_fields` entry is checked by type:
|
|
1278
|
+
|
|
1279
|
+
- **String/text** — substring match (`ILIKE '%term%'`)
|
|
1280
|
+
- **Integer/float/decimal** — exact match only when the search term is numeric
|
|
1281
|
+
- **Date/datetime** — range match only when the search term parses as a date
|
|
1282
|
+
- **Enum** — matches against display labels, not raw values (e.g., searching "won" matches `closed_won` if its humanized label contains "won")
|
|
1283
|
+
|
|
1284
|
+
Fields that don't match the search term's type are silently skipped, so searching for "hello" on a deals index won't error on the `value` (decimal) field.
|
|
1285
|
+
|
|
1286
|
+
### Search Parameters
|
|
1287
|
+
|
|
1288
|
+
Search parameters add typed filter controls — dropdowns, toggles, and range inputs — directly in the search toolbar. They generate standard Ransack `?f[...]` URL params, so they work seamlessly with the existing filter pipeline, saved filters, and export.
|
|
1289
|
+
|
|
1290
|
+
**YAML:**
|
|
1291
|
+
|
|
1292
|
+
```yaml
|
|
1293
|
+
search:
|
|
1294
|
+
enabled: true
|
|
1295
|
+
columns: 2
|
|
1296
|
+
searchable_fields: [name, email]
|
|
1297
|
+
parameters:
|
|
1298
|
+
- name: department
|
|
1299
|
+
type: association_select
|
|
1300
|
+
field: department_id
|
|
1301
|
+
|
|
1302
|
+
- name: status
|
|
1303
|
+
type: enum_select
|
|
1304
|
+
field: status
|
|
1305
|
+
|
|
1306
|
+
- name: active
|
|
1307
|
+
type: boolean_select
|
|
1308
|
+
field: active
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
**Ruby DSL:**
|
|
1312
|
+
|
|
1313
|
+
```ruby
|
|
1314
|
+
search do
|
|
1315
|
+
columns 2
|
|
1316
|
+
searchable_fields :name, :email
|
|
1317
|
+
parameter :department, type: :association_select, field: :department_id
|
|
1318
|
+
parameter :status, type: :enum_select, field: :status
|
|
1319
|
+
parameter :active, type: :boolean_select, field: :active
|
|
1320
|
+
end
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
Five parameter types are available: `enum_select`, `association_select`, `boolean_select`, `number_range`, and `date_range`. Enum options and association models are auto-derived from the model definition when not explicitly specified. Parameters render in a CSS Grid with configurable `columns` (default: 1). Labels display inline beside controls. Range types auto-span 2 columns when `columns >= 2`; use `col_span` per parameter to override.
|
|
1324
|
+
|
|
1325
|
+
For large association datasets, opt into remote server-side search with `search: true`:
|
|
1326
|
+
|
|
1327
|
+
```yaml
|
|
1328
|
+
- name: company
|
|
1329
|
+
type: association_select
|
|
1330
|
+
field: company_id
|
|
1331
|
+
search: true
|
|
1332
|
+
per_page: 25
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
Parameters support role-based visibility via `visible_when`. Hidden parameters are not rendered and their filter params are stripped server-side for security:
|
|
1336
|
+
|
|
1337
|
+
```yaml
|
|
1338
|
+
- name: tenant
|
|
1339
|
+
type: association_select
|
|
1340
|
+
field: tenant_id
|
|
1341
|
+
visible_when:
|
|
1342
|
+
role: admin
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
See [Presenters Reference — Search Parameters](../reference/presenters.md#search-parameters) for the full attribute reference.
|
|
1346
|
+
|
|
1347
|
+
### Advanced Filter Builder
|
|
1348
|
+
|
|
1349
|
+
The advanced filter builder adds a visual UI for constructing field-level filter conditions. Users select a field, an operator, and a value. Multiple conditions combine with AND logic, with optional OR grouping.
|
|
1350
|
+
|
|
1351
|
+
**YAML:**
|
|
1352
|
+
|
|
1353
|
+
```yaml
|
|
1354
|
+
search:
|
|
1355
|
+
enabled: true
|
|
1356
|
+
searchable_fields: [title, description]
|
|
1357
|
+
|
|
1358
|
+
advanced_filter:
|
|
1359
|
+
enabled: true
|
|
1360
|
+
max_conditions: 20
|
|
1361
|
+
max_association_depth: 3
|
|
1362
|
+
allow_or_groups: true
|
|
1363
|
+
|
|
1364
|
+
filterable_fields:
|
|
1365
|
+
- title
|
|
1366
|
+
- stage
|
|
1367
|
+
- value
|
|
1368
|
+
- expected_close_date
|
|
1369
|
+
- company.name
|
|
1370
|
+
- company.industry
|
|
1371
|
+
- contact.email
|
|
1372
|
+
|
|
1373
|
+
field_options:
|
|
1374
|
+
stage:
|
|
1375
|
+
operators: [eq, not_eq, in, not_in]
|
|
1376
|
+
value:
|
|
1377
|
+
operators: [eq, gt, gteq, lt, lteq, between]
|
|
1378
|
+
|
|
1379
|
+
presets:
|
|
1380
|
+
- name: high_value_open
|
|
1381
|
+
label: "High-value open deals"
|
|
1382
|
+
conditions:
|
|
1383
|
+
- { field: stage, operator: not_in, value: [closed_won, closed_lost] }
|
|
1384
|
+
- { field: value, operator: gteq, value: 10000 }
|
|
1385
|
+
|
|
1386
|
+
custom_filters:
|
|
1387
|
+
- name: region
|
|
1388
|
+
label: "Region"
|
|
1389
|
+
type: string
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
**Ruby DSL:**
|
|
1393
|
+
|
|
1394
|
+
```ruby
|
|
1395
|
+
search do
|
|
1396
|
+
searchable_fields :title, :description
|
|
1397
|
+
|
|
1398
|
+
advanced_filter do
|
|
1399
|
+
max_conditions 20
|
|
1400
|
+
max_association_depth 3
|
|
1401
|
+
allow_or_groups true
|
|
1402
|
+
|
|
1403
|
+
filterable_field :title
|
|
1404
|
+
filterable_field :stage, operators: [:eq, :not_eq, :in, :not_in]
|
|
1405
|
+
filterable_field :value, operators: [:eq, :gt, :gteq, :lt, :lteq, :between]
|
|
1406
|
+
filterable_field :company, :name
|
|
1407
|
+
filterable_field :contact, :email
|
|
1408
|
+
|
|
1409
|
+
preset "high_value_open", label: "High-value open deals" do
|
|
1410
|
+
condition field: :stage, operator: :not_in, value: %w[closed_won closed_lost]
|
|
1411
|
+
condition field: :value, operator: :gteq, value: 10000
|
|
1412
|
+
end
|
|
1413
|
+
end
|
|
1414
|
+
end
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
If `filterable_fields` is omitted, all readable model fields are auto-detected based on the current user's permissions.
|
|
1418
|
+
|
|
1419
|
+
### Filter Presets
|
|
1420
|
+
|
|
1421
|
+
Presets appear as pill-shaped buttons at the top of the filter panel. Clicking a preset immediately applies its conditions (page navigates with filter URL params). If the current URL filters match a preset exactly, the button is highlighted.
|
|
1422
|
+
|
|
1423
|
+
Presets support all operator types: equality, comparison, text matching, no-value operators (`present`, `true`, `this_month`), and multi-value operators (`in`, `not_in`). See the [presets reference](../reference/presenters.md#presets) for the full configuration format.
|
|
1424
|
+
|
|
1425
|
+
### Cascading Field Picker
|
|
1426
|
+
|
|
1427
|
+
When filtering by association fields (e.g., `company.country.name`), the filter builder renders a cascading drill-down selector instead of a flat dropdown. The first select shows direct fields and association names; selecting an association reveals a second select with that association's fields and sub-associations, and so on up to `max_association_depth`.
|
|
1428
|
+
|
|
1429
|
+
This approach keeps the initial dropdown manageable even for models with many associations and deep hierarchies.
|
|
1430
|
+
|
|
1431
|
+
### Operators by Field Type
|
|
1432
|
+
|
|
1433
|
+
Available operators are auto-detected from the field type. The full matrix is documented in the [Advanced Search design document](../design/advanced_search.md). Key highlights:
|
|
1434
|
+
|
|
1435
|
+
| Field Type | Notable Operators |
|
|
1436
|
+
|------------|-------------------|
|
|
1437
|
+
| string | `eq`, `cont`, `start`, `end`, `in`, `present`, `blank` |
|
|
1438
|
+
| integer, float, decimal | `eq`, `gt`, `gteq`, `lt`, `lteq`, `between`, `in` |
|
|
1439
|
+
| date, datetime | `eq`, `gt`, `lt`, `between`, `this_week`, `this_month`, `this_year` |
|
|
1440
|
+
| boolean | `true`, `false`, `null` |
|
|
1441
|
+
| enum | `eq`, `in`, `not_in`, `present`, `blank` |
|
|
1442
|
+
|
|
1443
|
+
Use `field_options` to restrict or customize operators for specific fields.
|
|
1444
|
+
|
|
1445
|
+
### Filtering by Association Fields
|
|
1446
|
+
|
|
1447
|
+
Dot notation lets users filter by fields on related models:
|
|
1448
|
+
|
|
1449
|
+
```yaml
|
|
1450
|
+
filterable_fields:
|
|
1451
|
+
- company.name # 1 level deep (belongs_to :company)
|
|
1452
|
+
- company.industry # 1 level deep
|
|
1453
|
+
- contact.company.country # 2 levels deep (contact → company → country)
|
|
1454
|
+
```
|
|
1455
|
+
|
|
1456
|
+
The `max_association_depth` setting (default: 3) limits how deep association chains can go. Each association segment is permission-checked — users can only filter on associations they have read access to.
|
|
1457
|
+
|
|
1458
|
+
### Query Language (QL) Mode
|
|
1459
|
+
|
|
1460
|
+
The query language provides a text-based alternative to the visual filter builder. Enable it via `query_language: true`:
|
|
1461
|
+
|
|
1462
|
+
```yaml
|
|
1463
|
+
search:
|
|
1464
|
+
advanced_filter:
|
|
1465
|
+
enabled: true
|
|
1466
|
+
query_language: true
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
Users toggle between visual and QL mode with the "Edit as QL" button. The two modes are bidirectional — changes in one are reflected in the other.
|
|
1470
|
+
|
|
1471
|
+
**QL syntax examples:**
|
|
1472
|
+
|
|
1473
|
+
```
|
|
1474
|
+
stage = 'lead' # equals
|
|
1475
|
+
value > 10000 # greater than
|
|
1476
|
+
title ~ 'Acme' # contains
|
|
1477
|
+
company.name = 'Acme Corp' # association field
|
|
1478
|
+
stage in ['lead', 'qualified'] # list membership
|
|
1479
|
+
expected_close_date is null # null check
|
|
1480
|
+
active is true # boolean check
|
|
1481
|
+
stage = 'lead' and value > 5000 # AND combinator
|
|
1482
|
+
(a = 1 or b = 2) and (c = 3 or d = 4) # nested groups
|
|
1483
|
+
@open_deals and value > 5000 # scope reference + condition
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
See the [Advanced Search design document](../design/advanced_search.md#8-query-language-ql) for the full operator mapping.
|
|
1487
|
+
|
|
1488
|
+
### Recursive Condition Nesting
|
|
1489
|
+
|
|
1490
|
+
By default, conditions combine with AND at the top level, with optional OR groups (two-level nesting). For deeper nesting, increase `max_nesting_depth`:
|
|
1491
|
+
|
|
1492
|
+
```yaml
|
|
1493
|
+
search:
|
|
1494
|
+
advanced_filter:
|
|
1495
|
+
enabled: true
|
|
1496
|
+
allow_or_groups: true
|
|
1497
|
+
max_nesting_depth: 3 # Default: 2 (AND with OR groups)
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
- `max_nesting_depth: 1` — flat AND only (no OR groups)
|
|
1501
|
+
- `max_nesting_depth: 2` — AND with OR groups (default)
|
|
1502
|
+
- `max_nesting_depth: 3+` — full recursive nesting (e.g., `(A AND B) OR (C AND D)`)
|
|
1503
|
+
|
|
1504
|
+
### Auto-Detect with Exclusions
|
|
1505
|
+
|
|
1506
|
+
Instead of listing all filterable fields explicitly, you can auto-detect all readable fields and exclude specific ones:
|
|
1507
|
+
|
|
1508
|
+
```yaml
|
|
1509
|
+
search:
|
|
1510
|
+
advanced_filter:
|
|
1511
|
+
enabled: true
|
|
1512
|
+
max_association_depth: 3
|
|
1513
|
+
filterable_fields_except:
|
|
1514
|
+
- internal_notes # exclude a direct field
|
|
1515
|
+
- audit_log # exclude entire association subtree
|
|
1516
|
+
- company.tax_id # exclude a specific association field
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
This is mutually exclusive with `filterable_fields` — use one or the other.
|
|
1520
|
+
|
|
1521
|
+
### Saved Filters
|
|
1522
|
+
|
|
1523
|
+
Saved filters let users name and store filter combinations for reuse. Enable via `saved_filters`:
|
|
1524
|
+
|
|
1525
|
+
```yaml
|
|
1526
|
+
search:
|
|
1527
|
+
saved_filters:
|
|
1528
|
+
enabled: true
|
|
1529
|
+
sharing: true # Allow sharing with roles
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
Full saved filter support (user-created, shared by role) is planned for a future release. Currently, filter presets defined in YAML serve as the static equivalent. See the [Advanced Search design document](../design/advanced_search.md) for details.
|
|
1533
|
+
|
|
1534
|
+
---
|
|
1535
|
+
|
|
1536
|
+
## Actions
|
|
1537
|
+
|
|
1538
|
+
Actions define the buttons available for creating, viewing, editing, deleting, and performing custom operations on records.
|
|
1539
|
+
|
|
1540
|
+
### Built-In CRUD Actions
|
|
1541
|
+
|
|
1542
|
+
**YAML:**
|
|
1543
|
+
|
|
1544
|
+
```yaml
|
|
1545
|
+
actions:
|
|
1546
|
+
collection:
|
|
1547
|
+
- { name: create, type: built_in, label: "New Contact", icon: plus }
|
|
1548
|
+
single:
|
|
1549
|
+
- { name: show, type: built_in, icon: eye }
|
|
1550
|
+
- { name: edit, type: built_in, icon: pencil }
|
|
1551
|
+
- { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
**Ruby DSL:**
|
|
1555
|
+
|
|
1556
|
+
```ruby
|
|
1557
|
+
action :create, type: :built_in, on: :collection, label: "New Contact", icon: "plus"
|
|
1558
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
1559
|
+
action :edit, type: :built_in, on: :single, icon: "pencil"
|
|
1560
|
+
action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
Actions are grouped into three categories:
|
|
1564
|
+
|
|
1565
|
+
| Category | Description |
|
|
1566
|
+
|----------|-------------|
|
|
1567
|
+
| `collection` / `on: :collection` | Actions above the table (e.g., "New") |
|
|
1568
|
+
| `single` / `on: :single` | Actions per row (e.g., show, edit, destroy) |
|
|
1569
|
+
| `batch` / `on: :batch` | Actions on multiple selected records |
|
|
1570
|
+
|
|
1571
|
+
### Custom Actions
|
|
1572
|
+
|
|
1573
|
+
Custom actions trigger domain-specific operations. Use `type: custom` and register an action class in `app/actions/`.
|
|
1574
|
+
|
|
1575
|
+
**YAML:**
|
|
1576
|
+
|
|
1577
|
+
```yaml
|
|
1578
|
+
actions:
|
|
1579
|
+
single:
|
|
1580
|
+
- name: close_won
|
|
1581
|
+
type: custom
|
|
1582
|
+
label: "Close as Won"
|
|
1583
|
+
icon: check-circle
|
|
1584
|
+
confirm: true
|
|
1585
|
+
confirm_message: "Mark this deal as won?"
|
|
1586
|
+
- name: send_invoice
|
|
1587
|
+
type: custom
|
|
1588
|
+
label: "Send Invoice"
|
|
1589
|
+
icon: mail
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
**Ruby DSL:**
|
|
1593
|
+
|
|
1594
|
+
```ruby
|
|
1595
|
+
action :close_won, type: :custom, on: :single,
|
|
1596
|
+
label: "Close as Won", icon: "check-circle",
|
|
1597
|
+
confirm: true, confirm_message: "Mark this deal as won?"
|
|
1598
|
+
|
|
1599
|
+
action :send_invoice, type: :custom, on: :single,
|
|
1600
|
+
label: "Send Invoice", icon: "mail"
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
See the [Custom Actions Guide](custom-actions.md) for how to implement action classes.
|
|
1604
|
+
|
|
1605
|
+
### Conditional Actions
|
|
1606
|
+
|
|
1607
|
+
Control when actions are visible or disabled using `visible_when` and `disable_when`.
|
|
1608
|
+
|
|
1609
|
+
**YAML:**
|
|
1610
|
+
|
|
1611
|
+
```yaml
|
|
1612
|
+
single:
|
|
1613
|
+
- name: close_won
|
|
1614
|
+
type: custom
|
|
1615
|
+
label: "Close as Won"
|
|
1616
|
+
icon: check-circle
|
|
1617
|
+
visible_when: { field: stage, operator: not_in, value: [closed_won, closed_lost] }
|
|
1618
|
+
disable_when: { field: value, operator: blank }
|
|
1619
|
+
- name: reopen
|
|
1620
|
+
type: custom
|
|
1621
|
+
label: "Reopen"
|
|
1622
|
+
icon: refresh-cw
|
|
1623
|
+
visible_when: { field: stage, operator: in, value: [closed_won, closed_lost] }
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
**Ruby DSL:**
|
|
1627
|
+
|
|
1628
|
+
```ruby
|
|
1629
|
+
action :close_won, type: :custom, on: :single,
|
|
1630
|
+
label: "Close as Won", icon: "check-circle",
|
|
1631
|
+
visible_when: { field: :stage, operator: :not_in, value: [:closed_won, :closed_lost] },
|
|
1632
|
+
disable_when: { field: :value, operator: :blank }
|
|
1633
|
+
|
|
1634
|
+
action :reopen, type: :custom, on: :single,
|
|
1635
|
+
label: "Reopen", icon: "refresh-cw",
|
|
1636
|
+
visible_when: { field: :stage, operator: :in, value: [:closed_won, :closed_lost] }
|
|
1637
|
+
```
|
|
1638
|
+
|
|
1639
|
+
An action can use both `visible_when` and `disable_when` together. Visibility is evaluated first -- if the action is hidden, `disable_when` has no effect.
|
|
1640
|
+
|
|
1641
|
+
### Form Actions
|
|
1642
|
+
|
|
1643
|
+
Form actions replace the default Save/Cancel buttons on create/edit forms with configurable submit buttons. Each button can have its own redirect target, set field values on submit, and be filtered by role or form context (create vs. update).
|
|
1644
|
+
|
|
1645
|
+
When `actions.form` is not configured, the default Save and Cancel buttons render automatically.
|
|
1646
|
+
|
|
1647
|
+
**YAML:**
|
|
1648
|
+
|
|
1649
|
+
```yaml
|
|
1650
|
+
actions:
|
|
1651
|
+
form:
|
|
1652
|
+
- name: save
|
|
1653
|
+
type: built_in
|
|
1654
|
+
style: primary
|
|
1655
|
+
redirect: show
|
|
1656
|
+
|
|
1657
|
+
- name: save_and_new
|
|
1658
|
+
label: "Save & New"
|
|
1659
|
+
redirect: new
|
|
1660
|
+
only_on: create
|
|
1661
|
+
|
|
1662
|
+
- name: save_and_stay
|
|
1663
|
+
label: "Save & Continue Editing"
|
|
1664
|
+
redirect: edit
|
|
1665
|
+
only_on: update
|
|
1666
|
+
|
|
1667
|
+
- name: cancel
|
|
1668
|
+
type: built_in
|
|
1669
|
+
style: secondary
|
|
1670
|
+
```
|
|
1671
|
+
|
|
1672
|
+
**Ruby DSL:**
|
|
1673
|
+
|
|
1674
|
+
```ruby
|
|
1675
|
+
form_action :save, type: :built_in, style: :primary, redirect: :show
|
|
1676
|
+
form_action :save_and_new, label: "Save & New", redirect: :new, only_on: :create
|
|
1677
|
+
form_action :save_and_stay, label: "Save & Continue Editing",
|
|
1678
|
+
redirect: :edit, only_on: :update
|
|
1679
|
+
form_action :cancel, type: :built_in, style: :secondary
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
Use `set_fields` to inject values when a specific button is pressed. This is useful for "Save as Draft" vs. "Publish" patterns:
|
|
1683
|
+
|
|
1684
|
+
```yaml
|
|
1685
|
+
actions:
|
|
1686
|
+
form:
|
|
1687
|
+
- name: save_draft
|
|
1688
|
+
label: "Save Draft"
|
|
1689
|
+
redirect: edit
|
|
1690
|
+
|
|
1691
|
+
- name: save_and_publish
|
|
1692
|
+
label: "Publish"
|
|
1693
|
+
style: primary
|
|
1694
|
+
icon: globe
|
|
1695
|
+
set_fields:
|
|
1696
|
+
status: published
|
|
1697
|
+
published_at: { date: now }
|
|
1698
|
+
confirm: true
|
|
1699
|
+
redirect: show
|
|
1700
|
+
visible_when: { field: status, operator: not_eq, value: published }
|
|
1701
|
+
|
|
1702
|
+
- { name: cancel, type: built_in }
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
`set_fields` values override user input — if the user submits `status: draft` but the form action says `status: published`, the configured value wins.
|
|
1706
|
+
|
|
1707
|
+
### Pipeline: Save + Workflow Transition
|
|
1708
|
+
|
|
1709
|
+
Form actions can chain multiple operations in a single transaction using `pipeline` or the `transition:` / `action:` sugar attributes.
|
|
1710
|
+
|
|
1711
|
+
**YAML:**
|
|
1712
|
+
|
|
1713
|
+
```yaml
|
|
1714
|
+
actions:
|
|
1715
|
+
form:
|
|
1716
|
+
- name: save_draft
|
|
1717
|
+
label: "Save Draft"
|
|
1718
|
+
style: secondary
|
|
1719
|
+
redirect: edit
|
|
1720
|
+
|
|
1721
|
+
- name: save_and_submit
|
|
1722
|
+
label: "Save & Submit"
|
|
1723
|
+
style: primary
|
|
1724
|
+
icon: send
|
|
1725
|
+
transition: submit
|
|
1726
|
+
confirm: true
|
|
1727
|
+
confirm_message: "Submit for approval?"
|
|
1728
|
+
visible_when: { field: status, operator: eq, value: draft }
|
|
1729
|
+
redirect: show
|
|
1730
|
+
|
|
1731
|
+
- { name: cancel, type: built_in }
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
**Ruby DSL:**
|
|
1735
|
+
|
|
1736
|
+
```ruby
|
|
1737
|
+
form_action :save_draft, label: "Save Draft", style: :secondary, redirect: :edit
|
|
1738
|
+
form_action :save_and_submit, label: "Save & Submit", style: :primary, icon: "send",
|
|
1739
|
+
transition: :submit, confirm: true,
|
|
1740
|
+
confirm_message: "Submit for approval?",
|
|
1741
|
+
visible_when: { field: :status, operator: :eq, value: "draft" },
|
|
1742
|
+
redirect: :show
|
|
1743
|
+
form_action :cancel, type: :built_in, style: :secondary
|
|
1744
|
+
```
|
|
1745
|
+
|
|
1746
|
+
The `transition: submit` sugar expands to `pipeline: [save, { transition: submit }]`. For more complex sequences, use `pipeline:` explicitly:
|
|
1747
|
+
|
|
1748
|
+
```yaml
|
|
1749
|
+
- name: save_submit_and_notify
|
|
1750
|
+
label: "Submit & Notify"
|
|
1751
|
+
pipeline:
|
|
1752
|
+
- save
|
|
1753
|
+
- { transition: submit }
|
|
1754
|
+
- { action: notify_approvers }
|
|
1755
|
+
confirm: true
|
|
1756
|
+
redirect: show
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
All pipeline steps run in a single transaction — if any step fails, the entire operation rolls back.
|
|
1760
|
+
|
|
1761
|
+
Form actions support `only_roles` / `except_roles` for role-based visibility, and the controller re-validates authorization at submit time to prevent parameter tampering.
|
|
1762
|
+
|
|
1763
|
+
See the [Action Buttons & Pipelines Guide](action-buttons.md) for the full walkthrough, including pipelines, dialog form actions, cross-presenter redirect, and show page pipelines. See the [Presenters Reference](../reference/presenters.md#form-actions) for the full attribute table.
|
|
1764
|
+
|
|
1765
|
+
---
|
|
1766
|
+
|
|
1767
|
+
## Conditional Rendering
|
|
1768
|
+
|
|
1769
|
+
Form fields and sections can be conditionally shown/hidden or enabled/disabled based on record values.
|
|
1770
|
+
|
|
1771
|
+
### Field-Value Conditions
|
|
1772
|
+
|
|
1773
|
+
Evaluated client-side with JavaScript for instant reactivity.
|
|
1774
|
+
|
|
1775
|
+
**YAML:**
|
|
1776
|
+
|
|
1777
|
+
```yaml
|
|
1778
|
+
fields:
|
|
1779
|
+
- field: expected_revenue
|
|
1780
|
+
input_type: number
|
|
1781
|
+
prefix: "$"
|
|
1782
|
+
visible_when: { field: stage, operator: not_in, value: [lead] }
|
|
1783
|
+
- field: close_reason
|
|
1784
|
+
input_type: textarea
|
|
1785
|
+
disable_when: { field: stage, operator: not_in, value: [closed_won, closed_lost] }
|
|
1786
|
+
```
|
|
1787
|
+
|
|
1788
|
+
**Ruby DSL:**
|
|
1789
|
+
|
|
1790
|
+
```ruby
|
|
1791
|
+
field :expected_revenue, input_type: :number, prefix: "$",
|
|
1792
|
+
visible_when: { field: :stage, operator: :not_in, value: ["lead"] }
|
|
1793
|
+
|
|
1794
|
+
field :close_reason, input_type: :textarea,
|
|
1795
|
+
disable_when: { field: :stage, operator: :not_in, value: ["closed_won", "closed_lost"] }
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
### Service Conditions
|
|
1799
|
+
|
|
1800
|
+
For server-side logic (database lookups, complex business rules), use service conditions.
|
|
1801
|
+
|
|
1802
|
+
**YAML:**
|
|
1803
|
+
|
|
1804
|
+
```yaml
|
|
1805
|
+
fields:
|
|
1806
|
+
- field: internal_code
|
|
1807
|
+
visible_when: { service: persisted_check }
|
|
1808
|
+
```
|
|
1809
|
+
|
|
1810
|
+
**Ruby DSL:**
|
|
1811
|
+
|
|
1812
|
+
```ruby
|
|
1813
|
+
field :internal_code, visible_when: { service: :persisted_check }
|
|
1814
|
+
```
|
|
1815
|
+
|
|
1816
|
+
### Section-Level Conditions
|
|
1817
|
+
|
|
1818
|
+
Apply conditions to entire form sections.
|
|
1819
|
+
|
|
1820
|
+
**YAML:**
|
|
1821
|
+
|
|
1822
|
+
```yaml
|
|
1823
|
+
sections:
|
|
1824
|
+
- title: "Revenue Details"
|
|
1825
|
+
visible_when: { field: stage, operator: not_eq, value: lead }
|
|
1826
|
+
fields:
|
|
1827
|
+
- { field: expected_revenue, input_type: number }
|
|
1828
|
+
- { field: probability, input_type: slider }
|
|
1829
|
+
```
|
|
1830
|
+
|
|
1831
|
+
**Ruby DSL:**
|
|
1832
|
+
|
|
1833
|
+
```ruby
|
|
1834
|
+
section "Revenue Details",
|
|
1835
|
+
visible_when: { field: :stage, operator: :not_eq, value: "lead" } do
|
|
1836
|
+
field :expected_revenue, input_type: :number
|
|
1837
|
+
field :probability, input_type: :slider
|
|
1838
|
+
end
|
|
1839
|
+
```
|
|
1840
|
+
|
|
1841
|
+
For full details on condition operators and service conditions, see the [Conditional Rendering Guide](conditional-rendering.md) and [Condition Operators Reference](../reference/condition-operators.md).
|
|
1842
|
+
|
|
1843
|
+
---
|
|
1844
|
+
|
|
1845
|
+
## Read-Only and Embeddable Presenters
|
|
1846
|
+
|
|
1847
|
+
### Read-Only Presenters
|
|
1848
|
+
|
|
1849
|
+
Set `read_only: true` to disable create, edit, and destroy operations. The model data is still writable through other presenters or direct code. Use this for dashboards or reporting views.
|
|
1850
|
+
|
|
1851
|
+
**YAML:**
|
|
1852
|
+
|
|
1853
|
+
```yaml
|
|
1854
|
+
presenter:
|
|
1855
|
+
name: deal_pipeline
|
|
1856
|
+
model: deal
|
|
1857
|
+
label: "Pipeline"
|
|
1858
|
+
slug: pipeline
|
|
1859
|
+
icon: bar-chart
|
|
1860
|
+
read_only: true
|
|
1861
|
+
|
|
1862
|
+
index:
|
|
1863
|
+
table_columns:
|
|
1864
|
+
- { field: title, link_to: show, sortable: true }
|
|
1865
|
+
- field: stage
|
|
1866
|
+
renderer: badge
|
|
1867
|
+
options:
|
|
1868
|
+
color_map:
|
|
1869
|
+
open: blue
|
|
1870
|
+
closed_won: green
|
|
1871
|
+
closed_lost: red
|
|
1872
|
+
- { field: value, renderer: currency, summary: sum }
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
**Ruby DSL:**
|
|
1876
|
+
|
|
1877
|
+
```ruby
|
|
1878
|
+
define_presenter :deal_pipeline do
|
|
1879
|
+
model :deal
|
|
1880
|
+
label "Pipeline"
|
|
1881
|
+
slug "pipeline"
|
|
1882
|
+
icon "bar-chart"
|
|
1883
|
+
read_only true
|
|
1884
|
+
|
|
1885
|
+
index do
|
|
1886
|
+
column :title, link_to: :show, sortable: true
|
|
1887
|
+
column :stage, renderer: :badge,
|
|
1888
|
+
options: { color_map: { open: "blue", closed_won: "green", closed_lost: "red" } }
|
|
1889
|
+
column :value, renderer: :currency, summary: :sum
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
1893
|
+
end
|
|
1894
|
+
```
|
|
1895
|
+
|
|
1896
|
+
### Embeddable Presenters
|
|
1897
|
+
|
|
1898
|
+
Set `embeddable: true` to mark a presenter for embedding within other views (e.g., as an inline table within a parent record's show page). This is a metadata flag for the UI layer.
|
|
1899
|
+
|
|
1900
|
+
**YAML:**
|
|
1901
|
+
|
|
1902
|
+
```yaml
|
|
1903
|
+
presenter:
|
|
1904
|
+
name: deal_embed
|
|
1905
|
+
model: deal
|
|
1906
|
+
label: "Deals"
|
|
1907
|
+
embeddable: true
|
|
1908
|
+
# no slug -- not directly routable
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
**Ruby DSL:**
|
|
1912
|
+
|
|
1913
|
+
```ruby
|
|
1914
|
+
define_presenter :deal_embed do
|
|
1915
|
+
model :deal
|
|
1916
|
+
label "Deals"
|
|
1917
|
+
embeddable true
|
|
1918
|
+
end
|
|
1919
|
+
```
|
|
1920
|
+
|
|
1921
|
+
---
|
|
1922
|
+
|
|
1923
|
+
## DSL Inheritance
|
|
1924
|
+
|
|
1925
|
+
The Ruby DSL supports inheritance, where a child presenter copies the parent's configuration and overrides specific sections. This avoids duplication when you need multiple views of the same model.
|
|
1926
|
+
|
|
1927
|
+
**Ruby DSL:**
|
|
1928
|
+
|
|
1929
|
+
```ruby
|
|
1930
|
+
# config/lcp_ruby/presenters/deal.rb
|
|
1931
|
+
define_presenter :deal do
|
|
1932
|
+
model :deal
|
|
1933
|
+
label "Deals"
|
|
1934
|
+
slug "deals"
|
|
1935
|
+
icon "dollar-sign"
|
|
1936
|
+
|
|
1937
|
+
index do
|
|
1938
|
+
default_sort :created_at, :desc
|
|
1939
|
+
per_page 25
|
|
1940
|
+
column :title, link_to: :show, sortable: true
|
|
1941
|
+
column :stage, renderer: :badge, sortable: true
|
|
1942
|
+
column :value, renderer: :currency, sortable: true
|
|
1943
|
+
column :updated_at, renderer: :relative_date
|
|
1944
|
+
end
|
|
1945
|
+
|
|
1946
|
+
show do
|
|
1947
|
+
section "Deal Information", columns: 2 do
|
|
1948
|
+
field :title, renderer: :heading
|
|
1949
|
+
field :stage, renderer: :badge
|
|
1950
|
+
field :value, renderer: :currency
|
|
1951
|
+
end
|
|
1952
|
+
end
|
|
1953
|
+
|
|
1954
|
+
form do
|
|
1955
|
+
section "Details", columns: 2 do
|
|
1956
|
+
field :title, placeholder: "Deal title..."
|
|
1957
|
+
field :stage, input_type: :select
|
|
1958
|
+
field :value, input_type: :number, prefix: "$"
|
|
1959
|
+
end
|
|
1960
|
+
end
|
|
1961
|
+
|
|
1962
|
+
search do
|
|
1963
|
+
searchable_fields :title
|
|
1964
|
+
placeholder "Search deals..."
|
|
1965
|
+
filter :all, label: "All", default: true
|
|
1966
|
+
filter :open, label: "Open", scope: :open_deals
|
|
1967
|
+
end
|
|
1968
|
+
|
|
1969
|
+
action :create, type: :built_in, on: :collection, label: "New Deal", icon: "plus"
|
|
1970
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
1971
|
+
action :edit, type: :built_in, on: :single, icon: "pencil"
|
|
1972
|
+
action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
|
|
1973
|
+
end
|
|
1974
|
+
```
|
|
1975
|
+
|
|
1976
|
+
```ruby
|
|
1977
|
+
# config/lcp_ruby/presenters/deal_pipeline.rb
|
|
1978
|
+
define_presenter :deal_pipeline, inherits: :deal do
|
|
1979
|
+
label "Deal Pipeline"
|
|
1980
|
+
slug "pipeline"
|
|
1981
|
+
icon "bar-chart"
|
|
1982
|
+
read_only true
|
|
1983
|
+
|
|
1984
|
+
# Completely replaces the parent's index
|
|
1985
|
+
index do
|
|
1986
|
+
default_view :table
|
|
1987
|
+
per_page 50
|
|
1988
|
+
column :title, link_to: :show, sortable: true
|
|
1989
|
+
column :stage, renderer: :badge, sortable: true
|
|
1990
|
+
column :value, renderer: :currency, summary: :sum
|
|
1991
|
+
end
|
|
1992
|
+
|
|
1993
|
+
# Disables search
|
|
1994
|
+
search enabled: false
|
|
1995
|
+
|
|
1996
|
+
# Replaces parent's actions
|
|
1997
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
1998
|
+
end
|
|
1999
|
+
```
|
|
2000
|
+
|
|
2001
|
+
### Inheritance Rules
|
|
2002
|
+
|
|
2003
|
+
- **Section-level replace**: When a child defines `index`, `show`, `form`, `search`, or `actions`, it completely replaces the parent's version. There is no deep merge.
|
|
2004
|
+
- **Unset sections are inherited**: If the child does not define a section, the parent's version is used as-is.
|
|
2005
|
+
- **Top-level attributes**: `name`, `label`, `slug`, `icon`, `read_only`, `embeddable` are taken from the child when defined. The `model` is inherited from the parent unless overridden.
|
|
2006
|
+
- **Parent must exist**: The parent must be defined in a DSL file in the same directory.
|
|
2007
|
+
- **No circular inheritance**: Circular chains are detected and raise `MetadataError`.
|
|
2008
|
+
|
|
2009
|
+
Inheritance is a DSL-only convenience -- YAML files cannot inherit. The result is always a flat hash identical to what you could write in full YAML.
|
|
2010
|
+
|
|
2011
|
+
---
|
|
2012
|
+
|
|
2013
|
+
## Common Patterns
|
|
2014
|
+
|
|
2015
|
+
### CRM Contact List
|
|
2016
|
+
|
|
2017
|
+
A contact list with search, filters, responsive columns, and multiple renderers.
|
|
2018
|
+
|
|
2019
|
+
**YAML:**
|
|
2020
|
+
|
|
2021
|
+
```yaml
|
|
2022
|
+
presenter:
|
|
2023
|
+
name: contact
|
|
2024
|
+
model: contact
|
|
2025
|
+
label: "Contacts"
|
|
2026
|
+
slug: contacts
|
|
2027
|
+
icon: users
|
|
2028
|
+
|
|
2029
|
+
index:
|
|
2030
|
+
default_sort: { field: name, direction: asc }
|
|
2031
|
+
per_page: 25
|
|
2032
|
+
row_click: show
|
|
2033
|
+
table_columns:
|
|
2034
|
+
- { field: name, width: "25%", link_to: show, sortable: true, pinned: left }
|
|
2035
|
+
- { field: email, renderer: email_link, hidden_on: mobile }
|
|
2036
|
+
- { field: phone, renderer: phone_link, hidden_on: [mobile, tablet] }
|
|
2037
|
+
- field: company_name
|
|
2038
|
+
sortable: true
|
|
2039
|
+
hidden_on: mobile
|
|
2040
|
+
- field: status
|
|
2041
|
+
renderer: badge
|
|
2042
|
+
options:
|
|
2043
|
+
color_map:
|
|
2044
|
+
active: green
|
|
2045
|
+
inactive: gray
|
|
2046
|
+
|
|
2047
|
+
show:
|
|
2048
|
+
layout:
|
|
2049
|
+
- section: "Contact Details"
|
|
2050
|
+
columns: 2
|
|
2051
|
+
responsive:
|
|
2052
|
+
mobile:
|
|
2053
|
+
columns: 1
|
|
2054
|
+
fields:
|
|
2055
|
+
- { field: name, renderer: heading, col_span: 2 }
|
|
2056
|
+
- { field: email, renderer: email_link }
|
|
2057
|
+
- { field: phone, renderer: phone_link }
|
|
2058
|
+
- { field: company_name }
|
|
2059
|
+
- field: status
|
|
2060
|
+
renderer: badge
|
|
2061
|
+
- section: "Deals"
|
|
2062
|
+
type: association_list
|
|
2063
|
+
association: deals
|
|
2064
|
+
|
|
2065
|
+
form:
|
|
2066
|
+
sections:
|
|
2067
|
+
- title: "Contact Information"
|
|
2068
|
+
columns: 2
|
|
2069
|
+
responsive:
|
|
2070
|
+
mobile:
|
|
2071
|
+
columns: 1
|
|
2072
|
+
fields:
|
|
2073
|
+
- { field: first_name, autofocus: true }
|
|
2074
|
+
- { field: last_name }
|
|
2075
|
+
- { type: divider, label: "Communication" }
|
|
2076
|
+
- { field: email, placeholder: "email@example.com" }
|
|
2077
|
+
- { field: phone }
|
|
2078
|
+
- { field: company_id, input_type: association_select }
|
|
2079
|
+
- title: "Notes"
|
|
2080
|
+
collapsible: true
|
|
2081
|
+
fields:
|
|
2082
|
+
- { field: notes, input_type: textarea, input_options: { rows: 4 } }
|
|
2083
|
+
|
|
2084
|
+
search:
|
|
2085
|
+
enabled: true
|
|
2086
|
+
searchable_fields: [first_name, last_name, email]
|
|
2087
|
+
placeholder: "Search contacts..."
|
|
2088
|
+
predefined_filters:
|
|
2089
|
+
- { name: all, label: "All", default: true }
|
|
2090
|
+
- { name: active, label: "Active", scope: active }
|
|
2091
|
+
- { name: inactive, label: "Inactive", scope: inactive }
|
|
2092
|
+
|
|
2093
|
+
actions:
|
|
2094
|
+
collection:
|
|
2095
|
+
- { name: create, type: built_in, label: "New Contact", icon: plus }
|
|
2096
|
+
single:
|
|
2097
|
+
- { name: show, type: built_in, icon: eye }
|
|
2098
|
+
- { name: edit, type: built_in, icon: pencil }
|
|
2099
|
+
- { name: destroy, type: built_in, icon: trash, confirm: true, style: danger }
|
|
2100
|
+
```
|
|
2101
|
+
|
|
2102
|
+
### Order Form with Line Items
|
|
2103
|
+
|
|
2104
|
+
A form with nested fields for inline editing of child records.
|
|
2105
|
+
|
|
2106
|
+
**Ruby DSL:**
|
|
2107
|
+
|
|
2108
|
+
```ruby
|
|
2109
|
+
define_presenter :order do
|
|
2110
|
+
model :order
|
|
2111
|
+
label "Orders"
|
|
2112
|
+
slug "orders"
|
|
2113
|
+
icon "shopping-cart"
|
|
2114
|
+
|
|
2115
|
+
index do
|
|
2116
|
+
default_sort :created_at, :desc
|
|
2117
|
+
per_page 20
|
|
2118
|
+
column :order_number, link_to: :show, sortable: true
|
|
2119
|
+
column :customer_name, sortable: true
|
|
2120
|
+
column :total, renderer: :currency, sortable: true, summary: :sum,
|
|
2121
|
+
options: { currency: "$", precision: 2 }
|
|
2122
|
+
column :status, renderer: :badge,
|
|
2123
|
+
options: { color_map: { pending: "yellow", shipped: "blue", delivered: "green" } }
|
|
2124
|
+
column :created_at, renderer: :relative_date, hidden_on: :mobile
|
|
2125
|
+
end
|
|
2126
|
+
|
|
2127
|
+
form do
|
|
2128
|
+
section "Order Details", columns: 2, responsive: { mobile: 1 } do
|
|
2129
|
+
field :customer_id, input_type: :association_select
|
|
2130
|
+
field :order_date, input_type: :date, default: "current_date"
|
|
2131
|
+
field :status, input_type: :select
|
|
2132
|
+
field :notes, input_type: :textarea, col_span: 2,
|
|
2133
|
+
input_options: { rows: 3 }
|
|
2134
|
+
end
|
|
2135
|
+
|
|
2136
|
+
nested_fields "Line Items", association: :line_items,
|
|
2137
|
+
sortable: true, allow_add: true, allow_remove: true,
|
|
2138
|
+
add_label: "Add Line Item", min: 1, max: 50,
|
|
2139
|
+
empty_message: "Add at least one line item." do
|
|
2140
|
+
field :product_id, input_type: :association_select
|
|
2141
|
+
field :quantity, input_type: :number, input_options: { min: 1 }
|
|
2142
|
+
field :unit_price, input_type: :number, prefix: "$",
|
|
2143
|
+
input_options: { min: 0, step: 0.01 }
|
|
2144
|
+
end
|
|
2145
|
+
end
|
|
2146
|
+
|
|
2147
|
+
search do
|
|
2148
|
+
searchable_fields :order_number, :customer_name
|
|
2149
|
+
placeholder "Search orders..."
|
|
2150
|
+
filter :all, label: "All", default: true
|
|
2151
|
+
filter :pending, label: "Pending", scope: :pending
|
|
2152
|
+
filter :shipped, label: "Shipped", scope: :shipped
|
|
2153
|
+
end
|
|
2154
|
+
|
|
2155
|
+
action :create, type: :built_in, on: :collection, label: "New Order", icon: "plus"
|
|
2156
|
+
action :show, type: :built_in, on: :single, icon: "eye"
|
|
2157
|
+
action :edit, type: :built_in, on: :single, icon: "pencil"
|
|
2158
|
+
action :destroy, type: :built_in, on: :single, icon: "trash", confirm: true, style: :danger
|
|
2159
|
+
end
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
---
|
|
2163
|
+
|
|
2164
|
+
## Grouped Presenters
|
|
2165
|
+
|
|
2166
|
+
A grouped presenter runs a GROUP BY query instead of a flat record scan. Use it for reporting views, summary tables, and drill-through analytics — for example, showing product counts and total revenue broken down by category, or order volume grouped by month.
|
|
2167
|
+
|
|
2168
|
+
Grouped presenters are read-only: they have no show, edit, or form views, and no CRUD actions. They are typically paired with a regular presenter via `row_click` so users can drill into the underlying records.
|
|
2169
|
+
|
|
2170
|
+
### Basic Grouped Presenter
|
|
2171
|
+
|
|
2172
|
+
**YAML:**
|
|
2173
|
+
|
|
2174
|
+
```yaml
|
|
2175
|
+
presenter:
|
|
2176
|
+
name: products_by_category
|
|
2177
|
+
model: product
|
|
2178
|
+
label: "Products by Category"
|
|
2179
|
+
slug: products-by-category
|
|
2180
|
+
|
|
2181
|
+
index:
|
|
2182
|
+
query_mode: grouped
|
|
2183
|
+
group_by: [category]
|
|
2184
|
+
default_sort: { field: product_count, direction: desc }
|
|
2185
|
+
row_click:
|
|
2186
|
+
mode: filter
|
|
2187
|
+
target: products
|
|
2188
|
+
table_columns:
|
|
2189
|
+
- { field: category, sortable: true }
|
|
2190
|
+
- { field: product_count, aggregate: count, sortable: true }
|
|
2191
|
+
- { field: total_price, aggregate: { function: sum, field: price }, sortable: true }
|
|
2192
|
+
```
|
|
2193
|
+
|
|
2194
|
+
**Ruby DSL:**
|
|
2195
|
+
|
|
2196
|
+
```ruby
|
|
2197
|
+
define_presenter :products_by_category do
|
|
2198
|
+
model :product
|
|
2199
|
+
label "Products by Category"
|
|
2200
|
+
slug "products-by-category"
|
|
2201
|
+
|
|
2202
|
+
index do
|
|
2203
|
+
query_mode :grouped
|
|
2204
|
+
group_by [:category]
|
|
2205
|
+
default_sort :product_count, :desc
|
|
2206
|
+
row_click :filter, target: "products"
|
|
2207
|
+
|
|
2208
|
+
column :category, sortable: true
|
|
2209
|
+
column :product_count, aggregate: :count, sortable: true
|
|
2210
|
+
column :total_price, aggregate: { function: :sum, field: :price }, sortable: true
|
|
2211
|
+
end
|
|
2212
|
+
end
|
|
2213
|
+
```
|
|
2214
|
+
|
|
2215
|
+
`row_click: { mode: filter, target: "products" }` means clicking a row navigates to the `products` presenter with Ransack filter params pre-filled from the group's field values. The user sees all products in that category without building a filter manually.
|
|
2216
|
+
|
|
2217
|
+
### HAVING and Limit
|
|
2218
|
+
|
|
2219
|
+
Use `having` to filter groups after aggregation, and `limit` to cap the number of rows (useful for "top N" tables):
|
|
2220
|
+
|
|
2221
|
+
**Ruby DSL:**
|
|
2222
|
+
|
|
2223
|
+
```ruby
|
|
2224
|
+
index do
|
|
2225
|
+
query_mode :grouped
|
|
2226
|
+
group_by [:category]
|
|
2227
|
+
having { { field: :product_count, operator: :gte, value: 5 } }
|
|
2228
|
+
limit 10
|
|
2229
|
+
default_sort :total_price, :desc
|
|
2230
|
+
|
|
2231
|
+
column :category, sortable: true
|
|
2232
|
+
column :product_count, aggregate: :count, sortable: true
|
|
2233
|
+
column :total_price, aggregate: { function: :sum, field: :price }, sortable: true
|
|
2234
|
+
end
|
|
2235
|
+
```
|
|
2236
|
+
|
|
2237
|
+
**YAML:**
|
|
2238
|
+
|
|
2239
|
+
```yaml
|
|
2240
|
+
index:
|
|
2241
|
+
query_mode: grouped
|
|
2242
|
+
group_by: [category]
|
|
2243
|
+
having:
|
|
2244
|
+
- { field: product_count, operator: gte, value: 5 }
|
|
2245
|
+
limit: 10
|
|
2246
|
+
default_sort: { field: total_price, direction: desc }
|
|
2247
|
+
table_columns:
|
|
2248
|
+
- { field: category, sortable: true }
|
|
2249
|
+
- { field: product_count, aggregate: count, sortable: true }
|
|
2250
|
+
- { field: total_price, aggregate: { function: sum, field: price }, sortable: true }
|
|
2251
|
+
```
|
|
2252
|
+
|
|
2253
|
+
With `limit: 10`, pagination is disabled and only the top 10 groups are returned. This is ideal for embedding in dashboard zones.
|
|
2254
|
+
|
|
2255
|
+
### Date Period Grouping
|
|
2256
|
+
|
|
2257
|
+
Group by a date field truncated to a period. The column uses `group_field` to reference the source field when the column name differs:
|
|
2258
|
+
|
|
2259
|
+
**Ruby DSL:**
|
|
2260
|
+
|
|
2261
|
+
```ruby
|
|
2262
|
+
define_presenter :orders_by_month do
|
|
2263
|
+
model :order
|
|
2264
|
+
label "Orders by Month"
|
|
2265
|
+
slug "orders-by-month"
|
|
2266
|
+
|
|
2267
|
+
index do
|
|
2268
|
+
query_mode :grouped
|
|
2269
|
+
group_by [{ field: :created_at, period: :month }]
|
|
2270
|
+
default_sort :month, :desc
|
|
2271
|
+
|
|
2272
|
+
column :month, group_field: :created_at, sortable: true
|
|
2273
|
+
column :order_count, aggregate: :count, sortable: true
|
|
2274
|
+
column :revenue, aggregate: { function: :sum, field: :total }, sortable: true,
|
|
2275
|
+
renderer: :currency, options: { currency: "$", precision: 2 }
|
|
2276
|
+
end
|
|
2277
|
+
end
|
|
2278
|
+
```
|
|
2279
|
+
|
|
2280
|
+
Available periods: `year`, `quarter`, `month`, `week`, `day`. The database truncates the timestamp to the period boundary using the appropriate SQL function (`DATE_TRUNC` on PostgreSQL, `strftime` on SQLite).
|
|
2281
|
+
|
|
2282
|
+
### Aggregate Functions
|
|
2283
|
+
|
|
2284
|
+
| Short form | Full form | Description |
|
|
2285
|
+
|------------|-----------|-------------|
|
|
2286
|
+
| `count` | `{ function: count }` | Count of rows in the group |
|
|
2287
|
+
| — | `{ function: sum, field: amount }` | Sum of a numeric field |
|
|
2288
|
+
| — | `{ function: avg, field: price }` | Average of a numeric field |
|
|
2289
|
+
| — | `{ function: min, field: created_at }` | Minimum value |
|
|
2290
|
+
| — | `{ function: max, field: score }` | Maximum value |
|
|
2291
|
+
| — | `{ function: count, distinct: true, field: user_id }` | Count of distinct values |
|
|
2292
|
+
|
|
2293
|
+
All aggregate columns support `sortable: true` and renderers (e.g., `renderer: :currency`, `renderer: :number`).
|
|
2294
|
+
|
|
2295
|
+
### Enum Sort Order
|
|
2296
|
+
|
|
2297
|
+
Enum group columns default to sorting by **definition order** — the position in the model's enum values list (e.g., pipeline stages appear in logical order, not alphabetical). Override with `sort_strategy: value` for alphabetical sorting:
|
|
2298
|
+
|
|
2299
|
+
```yaml
|
|
2300
|
+
# Sorts by enum definition order (default for enums)
|
|
2301
|
+
- { field: stage, sortable: true }
|
|
2302
|
+
|
|
2303
|
+
# Sorts alphabetically by raw value
|
|
2304
|
+
- { field: stage, sortable: true, sort_strategy: value }
|
|
2305
|
+
```
|
|
2306
|
+
|
|
2307
|
+
```ruby
|
|
2308
|
+
# DSL equivalent
|
|
2309
|
+
column :stage, sortable: true, sort_strategy: :value
|
|
2310
|
+
```
|
|
2311
|
+
|
|
2312
|
+
This applies to both grouped and non-grouped index views. The implementation uses `CASE WHEN` SQL for definition-order sorting, so NULL values sort after all defined enum values.
|
|
2313
|
+
|
|
2314
|
+
---
|
|
2315
|
+
|
|
2316
|
+
## What's Next
|
|
2317
|
+
|
|
2318
|
+
- [Presenters Reference](../reference/presenters.md) -- complete attribute reference for all presenter YAML options
|
|
2319
|
+
- [Presenter DSL Reference](../reference/presenter-dsl.md) -- complete reference for the Ruby DSL with inheritance
|
|
2320
|
+
- [Renderers Guide](display-types.md) -- visual guide to all built-in renderers
|
|
2321
|
+
- [Conditional Rendering Guide](conditional-rendering.md) -- deep dive into `visible_when` and `disable_when`
|
|
2322
|
+
- [Custom Actions Guide](custom-actions.md) -- writing domain-specific action classes
|
|
2323
|
+
- [View Groups Guide](view-groups.md) -- navigation menu and view switching between presenters
|
|
2324
|
+
- [Condition Operators Reference](../reference/condition-operators.md) -- full list of supported operators
|