-
1
module LooposUi
-
1
class Accordion < LoopComponent
-
1
renders_one :body # TODO: deprecate this. We have the default content slot
-
1
renders_many :accordions, LooposUi::Accordion
-
1
renders_one :action_buttons, LooposUi::ActionButtons
-
-
1
renders_one :header, ->(title: nil, description: nil, tooltip: nil, size: nil, **kwargs) {
-
@size = size || :medium
-
@size = @size.to_sym
-
then: 0
else: 0
@size = :medium if @size == :normal # TODO: backwards compatibility with TitleDescription
-
-
LooposUi::Header.new(
-
title: title,
-
description: description,
-
tooltip: tooltip,
-
size: @size,
-
**kwargs,
-
)
-
}
-
-
# shorthand_syntax, forwarded to the header slot
-
1
option :title, optional: true
-
1
option :description, optional: true
-
1
option :tooltip, optional: true
-
1
option :size, Types::Coercible::Symbol.enum(:normal, :small, :tiny), optional: true, default: -> { :normal }
-
-
1
option :open, default: proc { false }
-
1
option :overflow_x_auto, default: proc { false }, type: Types::Bool
-
-
1
def before_render
-
else: 0
then: 0
unless header?
-
with_header(title: title, description: description, tooltip: tooltip, size: size)
-
end
-
end
-
-
1
private
-
-
1
def accordion_id
-
"accordion_#{SecureRandom.hex(6)}"
-
end
-
-
1
def classes
-
[
-
"lui-accordion",
-
then: 0
else: 0
open ? "lui-accordion--open" : nil,
-
then: 0
else: 0
overflow_x_auto ? "lui-accordion--overflow-x-auto" : nil,
-
].compact.join(" ")
-
end
-
-
1
def content_or_body
-
content.presence || body
-
end
-
end
-
end
-
<div class="<%= classes %>" data-controller="accordion" data-accordion-open-value="<%= open %>">
-
<div class="lui-accordion__header">
-
<button class="cursor-pointer flex justify-start gap-1 flex-1 items-center" type="button" data-action="click->accordion#toggle"
-
data-accordion-target="trigger"
-
aria-controls="<%= accordion_id %>">
-
<%# TODO: this will be changed in V2, quickfix now%>
-
then: 0
else: 0
<div class="lui-accordion__icon <%= [:small, :tiny].include?(size.to_sym) ? "mt-[2px]" : "mt-1" %>">
-
<%= render LooposUi::Icon.new(icon: "fa-regular fa-chevron-down", size: 8) %>
-
</div>
-
<span class="lui-accordion__title flex justify-start gap-1">
-
<%= header %>
-
</span>
-
</button>
-
<div aria-controls="<%= accordion_id %>" class="lui-accordion__buttons">
-
<%= action_buttons %>
-
</div>
-
</div>
-
<div
-
id="<%= accordion_id %>"
-
class="lui-accordion__content"
-
data-accordion-target="content"
-
>
-
<div class="lui-accordion__content-inner">
-
then: 0
else: 0
<%= tag.div(content_or_body, class: "pb-4") if content_or_body.present? %>
-
<% accordions.each do |accordion| %>
-
<div class="lui-accordion__accordion-content">
-
<%= accordion %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ActionBar < LoopComponent
-
1
include ActionView::Helpers::UrlHelper
-
1
include LooposUi::ResourceAware
-
-
1
renders_one :action_buttons, LooposUi::ActionButtons
-
1
renders_one :breadcrumbs_list, LooposUi::BreadcrumbList
-
1
renders_one :item_navigation, "LooposUi::ActionBar::ItemNavigation"
-
1
renders_one :issue_card
-
-
1
option :current_action, optional: true
-
-
1
def before_render
-
then: 0
else: 0
if breadcrumbs_list.nil? && LooposUi.config.auto_breadcrumbs
-
auto_breadcrumbs = find_current_breadcrumbs
-
-
with_breadcrumbs_list do |list|
-
auto_breadcrumbs.each do |breadcrumb|
-
list.with_breadcrumb(href: breadcrumb[:path]).with_content(breadcrumb[:title])
-
end
-
end
-
end
-
end
-
-
1
private
-
-
1
def find_current_breadcrumbs
-
current_action = self.current_action.to_s || action_name
-
target_path = case current_action
-
when: 0
when "index"
-
request.path
-
# Sometimes we render the show view as a response to a POST request, or inline editing
-
when "show"
-
when: 0
# Not supported auto breadcrumbs for show action without resource
-
then: 0
else: 0
if resource.nil?
-
LooposUi.logger.warn(<<~ERROR)
-
Auto breadcrumbs for show action without resource is not supported.
-
You passed model: #{model.inspect} yet no resource was found.
-
ERROR
-
return []
-
end
-
request.path.gsub(%r{/\d+$}, "")
-
else
-
else: 0
# TODO: support for other actions
-
return []
-
end
-
-
items = LooposUi.config.sidebar.items
-
breadcrumbs = []
-
find_breadcrumbs_dfs(items, breadcrumbs, target_path, current_action)
-
-
if breadcrumbs.count == 1 && current_action == "index"
-
then: 0
# TODO: translations for "Overview"
-
else: 0
breadcrumbs
-
then: 0
else: 0
elsif current_action == "show"
-
breadcrumbs << { title: resource.model_title || "", path: "#" }
-
end
-
-
breadcrumbs
-
end
-
-
1
def find_breadcrumbs_dfs(items, breadcrumbs, target_path, action)
-
items.each do |item|
-
else: 0
then: 0
next unless item.is_a?(Hash)
-
-
else: 0
path = case item[:path]
-
when: 0
when String
-
item[:path]
-
when: 0
when Proc
-
instance_exec(&item[:path])
-
end || "/"
-
-
breadcrumbs << {
-
title: string_or_callable(item[:title] || item[:short_title]),
-
path: path,
-
}
-
then: 0
else: 0
return true if path == target_path
-
-
then: 0
else: 0
if item[:menu_items]
-
found = find_breadcrumbs_dfs(item[:menu_items], breadcrumbs, target_path, action)
-
then: 0
else: 0
return true if found
-
end
-
-
breadcrumbs.pop
-
end
-
-
false
-
end
-
-
1
def string_or_callable(value)
-
then: 0
else: 0
value.respond_to?(:call) ? instance_exec(&value) : value
-
end
-
-
1
class ItemNavigation < LoopComponent
-
1
option :title, Types::String
-
1
option :next_path, Types::String
-
1
option :previous_path, Types::String
-
end
-
end
-
end
-
<div class="lui-action_bar">
-
<%= breadcrumbs_list %>
-
then: 0
else: 0
<% if issue_card.present? %>
-
<%= issue_card %>
-
<% end %>
-
<%= item_navigation %>
-
<%= action_buttons %>
-
</div>
-
-
<%= tag.div(class: "lui-action_bar__item_navigation") do %>
-
<%= tag.a(href: previous_path, class: "lui-action_bar__item_navigation__previous") do %>
-
<%= render LooposUi::MIcon.new(:chevron_left) %>
-
<% end %>
-
<%= tag.span(title, class: "lui-action_bar__item_navigation__title") %>
-
<%= tag.a(href: next_path, class: "lui-action_bar__item_navigation__next") do %>
-
<%= render LooposUi::MIcon.new(:chevron_right) %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ActionButtons < ViewComponent::Base
-
1
renders_many :button_groups, "LooposUi::ActionButtons::ButtonGroup"
-
-
1
class ButtonGroup < ViewComponent::Base
-
1
renders_many :buttons, types: LooposUi::Button.presets
-
end
-
end
-
end
-
2
<div class="lui-action_buttons">
-
2
<% button_groups.each do |bg| %>
-
4
<%= bg %>
-
<% end %>
-
2
</div>
-
8
<%= tag.div(class: "lui-action_buttons__button-group") do %>
-
4
<% buttons.each do |button| %>
-
10
<%= button %>
-
<% end %>
-
<% end %>
-
# app/components/loopos_ui/action_menu.rb
-
1
module LooposUi
-
1
class ActionMenu < LoopComponent
-
1
include LooposUi::FaviconAware
-
-
1
renders_one :trigger, types: {
-
button: { renders: Button, as: :trigger_button },
-
slot: { renders: ->(&block) { capture(&block) }, as: :trigger },
-
}
-
-
1
option :options, Types::Array.of(Types::Hash)
-
1
option :portal_data_controllers, default: -> { [] }, type: Types::Array.of(Types::String)
-
-
1
def data_controllers
-
["modal", "form-submit", "pubsub"].push(*portal_data_controllers).join(" ")
-
end
-
-
1
def option_attributes(option)
-
then: 0
if option[:disabled].present?
-
{}
-
else: 0
else
-
option[:attributes] || {}
-
end
-
end
-
-
1
def toggle_attributes(option)
-
option[:toggle_attributes] || {}
-
end
-
end
-
end
-
<div
-
data-controller="action-menu"
-
data-action="modal:open@window->action-menu#disableTippy modal:close@window->action-menu#enableTippy"
-
class="lui-action-menu">
-
<div data-action-menu-target="trigger">
-
<%= trigger %>
-
</div>
-
<div data-action-menu-target="menu" class="hidden lui-action-menu__wrapper" data-controller="<%= data_controllers %>">
-
<div class="lui-action-menu__options" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
-
<% options.each do |option| %>
-
then: 0
else: 0
<%= tag.div(class: "contents #{option[:disabled] ? 'cursor-not-allowed' : 'cursor-pointer'}") do %>
-
then: 0
<% if option[:url] %>
-
<a class="lui-action-menu__option" href="<%= option[:url] %>" <%= tag.attributes(option_attributes(option)) %>>
-
<div class="lui-action-menu__option-text">
-
then: 0
else: 0
<%= tag.i(class: faviconize(option[:icon])) if option[:icon] %>
-
<span><%= option[:text] %></span>
-
then: 0
else: 0
<%= render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", size: "12", text: option[:tooltip]) if option[:tooltip]%>
-
</div>
-
then: 0
else: 0
<% if option[:toggle] %>
-
<%= render LooposUi::Toggle.new(
-
**toggle_attributes(option).merge(color: LooposUi::Colors.find("apps-800-primary", LooposUi.config.app_type))
-
) %>
-
<% end %>
-
</a>
-
else: 0
<% else %>
-
then: 0
else: 0
<div class="lui-action-menu__option <%= option_attributes(option)[:'data-action'] ? 'cursor-pointer' : '' %>" <%= tag.attributes(option_attributes(option)) %>>
-
then: 0
else: 0
<div class="lui-action-menu__option-text <%= option_attributes(option)[:'data-action'] ? 'cursor-pointer' : 'cursor-default' %>">
-
then: 0
else: 0
<%= tag.i(class: faviconize(option[:icon])) if option[:icon] %>
-
<span><%= option[:text] %></span>
-
then: 0
else: 0
<%= render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", size: "12", text: option[:tooltip]) if option[:tooltip] %>
-
</div>
-
then: 0
else: 0
<% if option[:toggle] %>
-
<%= render LooposUi::Toggle.new(
-
**toggle_attributes(option).merge(color: LooposUi::Colors.find("apps-800-primary"))
-
) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module AppInstance
-
1
class Card < LoopComponent
-
1
option :app_instance
-
1
option :policy,
-
Types::Bool |
-
Types.Interface(:view_details?) |
-
Types::Hash.schema(
-
view_details?: Types::Bool | Types.Interface(:call),
-
),
-
optional: true,
-
default: -> { true }
-
-
1
private
-
-
# Used policy methods, only has one method for now
-
# TODO: abstract policy options to a concern. Duplicated in ModelAssociationList
-
1
[:view_details].each do |method|
-
1
define_method("can_#{method}?") do
-
case policy
-
when: 0
when TrueClass, FalseClass
-
policy
-
when: 0
when Hash
-
then: 0
else: 0
then: 0
if policy[method]&.respond_to?(:call)
-
policy[method].call
-
else: 0
else
-
!!policy[method]
-
end
-
else: 0
else # Assume object responds to method
-
policy.public_send("#{method}?")
-
end
-
end
-
end
-
-
1
def tag_status
-
case app_instance.status
-
when: 0
when "creating", "updating", "deleting", "stopped"
-
{
-
kind: "warning",
-
icon: "fa-regular fa-circle-exclamation",
-
}
-
when: 0
when "created", "to_create", "running"
-
{
-
kind: "success",
-
icon: "fa-regular fa-check-circle",
-
}
-
when: 0
when "error", "error_creating", "error_running", "error_updating", "error_deleting", "to_delete", "deleted", "partially_deleted"
-
{
-
kind: "danger",
-
icon: "fa-regular fa-circle-xmark",
-
}
-
else: 0
else
-
{
-
kind: "informative",
-
icon: "fa-regular fa-circle",
-
}
-
end
-
end
-
end
-
end
-
end
-
<%# TODO: migrate fully to UI, this is just a wrapper over an existing partial %>
-
-
<%
-
text_color =
-
when: 0
case app_instance.app.name
-
when: 0
when "core" then "!text-orange-700"
-
when: 0
when "submission" then "!text-yellow-700"
-
when: 0
when "validation" then "!text-teal-800"
-
else: 0
when "handling" then "text-[#19007D]!"
-
else "text-[#0069CA]!"
-
end %>
-
-
-
<div class="app-card" data-controller="app-cards" data-app-cards-target="overlay">
-
<div class="app-card__background"></div>
-
<div class="app-card__background-image-wrapper">
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<%= image_tag(app_instance.app.icon&.url || "loopos-icon.png", class:"app-card__background-image #{app_instance.app.icon&.url.present? ? "" : "gray-image"}")%>
-
</div>
-
<div class="absolute top-4 left-2 z-10 flex flex-col gap-1">
-
<p class="copy-12-medium max-w-[70%]"><%= app_instance.app_release.version_slug %></p>
-
then: 0
else: 0
<%= react_component("Tag", { text: app_instance.partnable&.name, variant: 'primary', kind: "manager", tSize: "large", line: "solid", imgURL: app_instance.partnable.logo.url }) %>
-
</div>
-
then: 0
else: 0
<% if helpers.current_user.can_view_app_instance_status? %>
-
<div class="absolute top-4 right-2 z-10">
-
<%= react_component("Tag", { text: app_instance.status, variant: 'primary', kind: tag_status.dig(:kind), tSize: "large", line: "solid", icon: tag_status.dig(:icon) }) %>
-
</div>
-
<% end %>
-
<div class="card__overlay ">
-
<div class="bg-glassify_intense rounded-full flex items-center absolute -top-6 right-2 p-1 cursor-pointer" data-action="click->app-cards#toggleOverlay" data-app-cards-target="icon"><i class="fa-regular fa-chevron-up text-xs text-black font-bold"></i></div>
-
<div class="card__header app-card__details-wrapper">
-
<div class="card__header-text">
-
<h3 class="app-card__name"><%= app_instance.displayed_label %></h3>
-
<span class="card__status">id: <%= app_instance.id %> </span>
-
</div>
-
<input type="checkbox" selected-id="<%= app_instance.id %>" class="<%= text_color %>">
-
</div>
-
<div class="card__description">
-
then: 0
else: 0
<% if can_view_details? %>
-
<a href="<%= admin_app_management_app_instance_path(app_instance) %>" target="_blank" >
-
<%= react_component("Tag", { text:"View Details", variant: 'primary', kind: "neutral", tSize: "extraLarge", line: "solid", icon:"fa-sharp fa-regular fa-arrow-up-right-from-square" }) %>
-
</a>
-
<% end %>
-
<a href="<%= app_instance.app_url %>" target="_blank" >
-
<%= react_component("Tag", { text:"Open", variant: 'primary', kind: "neutral", tSize: "extraLarge", line: "solid", icon:"fa-sharp fa-regular fa-arrow-up-right-from-square" }) %>
-
</a>
-
</div>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class AppLayoutComponent < LoopComponent
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
renders_one :header
-
1
renders_one :select_bar, "SelectBar"
-
1
renders_one :bottom_bar, LooposUi::BottomBar
-
-
# FIXME: When refactoring Sidebar, ensure that it gets the configuration from the initializer
-
1
renders_one :sidebar, ->(**kwargs) { LooposUi::Sidebar.new(**kwargs) }
-
-
1
option :title, optional: true # Not used yet, not documented
-
1
option :main_layout, default: -> { true }
-
-
1
def before_render
-
else: 0
then: 0
with_sidebar unless sidebar?
-
end
-
-
# TODO: document this and possibly refactor to real component
-
# But time is of essence, and the public facing API is good enough (check app_layout_preview/default.html.erb)
-
1
class SelectBar < LoopComponent
-
1
erb_template <<~ERB
-
<nav class="flex w-full min-h-8 box-content py-1.5 pr-4 pl-6 items-center space-between bg-general-gray-100 border-b border-gray-200">
-
<%= content %>
-
</nav>
-
ERB
-
-
1
def render?
-
has_any_selectors?
-
end
-
-
1
def has_any_selectors?
-
content.present? && Nokogiri::HTML(content.to_s).search(".lui-select-bar__selector").any?
-
end
-
-
1
class Selector < LoopComponent
-
1
erb_template <<-ERB
-
<div class="lui-select-bar__selector">
-
<%= render LooposUi::FormEntry.new(label: label, orientation: :horizontal) do |fe| %>
-
<% fe.with_input do %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: name,
-
options: options,
-
value: value,
-
placeholder: "Select..",
-
mode: :autosubmit,
-
)
-
%>
-
<% end %>
-
<% end %>
-
</div>
-
ERB
-
-
1
option :type, Types::Coercible::Symbol.enum(:core), default: -> { :core }
-
1
option :options, Types::Array.of(Types::Hash.schema(
-
value: Types::Coercible::String,
-
text: Types::String,
-
))
-
1
option :value, Types::Coercible::String
-
1
option :name, Types::Coercible::String
-
-
# TODO: these components are kinda hacked together
-
# and should be refactored internally. The FormEntry label does not support colors or icons, but
-
# with html_safe trick it works
-
1
def label
-
helpers.content_tag(:span, class: "text-app-800-primary") do
-
concat(helpers.content_tag(:icon, "", class: "fa-regular fa-map-pin mr-1"))
-
concat(type.to_s.capitalize)
-
concat(":")
-
end.html_safe
-
end
-
-
1
def render?
-
options.present? && options.count > 1
-
end
-
end
-
end
-
-
1
class RootStyles < LoopComponent
-
end
-
end
-
-
1
AppLayout = LooposUi::AppLayoutComponent # TODO: Alias for the component, deprecate next major version
-
# TODO: Alias for the component so it can be used as LooposUi::SelectBar Maybe we'll move it outside
-
1
SelectBar = LooposUi::AppLayoutComponent::SelectBar
-
end
-
<%# DEPRECATED: Use LooposUi::V2::AppLayout instead %>
-
-
<div class="flex grow flex-col w-full h-full"
-
data-controller="miniapps lui--global-modal"
-
data-action="showModal@window->lui--global-modal#showModal"
-
>
-
<%= render LooposUi::AppLayout::RootStyles.new %>
-
<div class="w-full lui-header-slot">
-
<%= header %>
-
</div>
-
<div class="flex grow flex-row overflow-hidden relative">
-
<%= sidebar %>
-
then: 0
else: 0
<div class="relative flex flex-col flex-[1_0_0] self-stretch items-start overflow-scroll lui-app-layout_id" style="<%= bottom_bar.present? ? 'height: calc(100% - 48px)' : '' %>">
-
<div class="h-full flex flex-col absolute left-0" style="width: 100%; transition: width 25ms ease-in;">
-
<%= select_bar %>
-
<%#= TODO: After migrating apps to not use main_layout, remove this conditional %>
-
then: 0
<% if main_layout.present? %>
-
then: 0
else: 0
<div class="<%= bottom_bar.present? ? 'pb-16' : '' %>">
-
<%= content %>
-
</div>
-
else: 0
<% else %>
-
<turbo-frame data-turbo-action="advance" class="h-full" id="lui-main-layout" data-turbo-frame="lui-main-layout">
-
<%
-
# I want to cal active_item but internaly it calls helpers (path etc) but I dont want to render the component just for that
-
# So we pass in the current view context manually
-
then: 0
else: 0
if LooposUi::Sidebar::USE_UI_2
-
sd = LooposUi::Sidebar::V2::Sidebar.new
-
sd.instance_variable_set(:@view_context, view_context)
-
end
-
%>
-
then: 0
else: 0
<%= helpers.turbo_stream.update("lui-sidebar-active-item-id", sd.active_item) if LooposUi::Sidebar::USE_UI_2 %>
-
<%= render LooposUi::LayoutLoading.new %>
-
<div class="lui-main_layout__content" data-skeleton-loading="<%= LooposUi.config.enable_loading_skeletons %>">
-
<%= content %>
-
</div>
-
</turbo-frame>
-
<% end %>
-
</div>
-
<div class="fixed bottom-0" style="width: calc(100% - var(--sidebar-width));">
-
<%= bottom_bar %>
-
</div>
-
</div>
-
</div>
-
<%= turbo_stream_from "lui-app-layout" %>
-
<%= turbo_stream_from :toasters %>
-
<div id="lui-toasters" popover="manual" data-controller="toasters" data-toasters-new-toaster-url-value="<%= LooposUi::Engine.routes.url_helpers.toasters_path %>">
-
</div>
-
</div>
-
18
<style>
-
:root {
-
18
--app-900-hover: <%= LooposUi::Colors.find("apps-900-hover") %>;
-
18
--app-800-primary: <%= LooposUi::Colors.find("apps-800-primary") %>;
-
18
--app-400: <%= LooposUi::Colors.find("apps-400") %>;
-
18
--app-300: <%= LooposUi::Colors.find("apps-300") %>;
-
18
--app-200: <%= LooposUi::Colors.find("apps-200") %>;
-
18
--app-100: <%= LooposUi::Colors.find("apps-100") %>;
-
18
--app-opacity5: <%= LooposUi::Colors.find("apps-opacity5") %>;
-
-
/* Current app variables, for semantic colors */
-
18
--current-surface-app: var(--color-surface-apps-<%= LooposUi.config.app_type %>);
-
18
--current-text-app: var(--color-text-apps-<%= LooposUi.config.app_type %>);
-
}
-
</style>
-
1
module LooposUi
-
1
class AppLogo < LoopComponent
-
1
require "stringio"
-
-
1
APPS = [:manager, :core, :submission, :validation, :handling, :hubs].freeze
-
1
option :app, Types::Coercible::Symbol.enum(*APPS)
-
1
option :expanded, Types::Strict::Bool, default: -> { false }
-
-
1
def app_logo_file
-
2
app_logo_file = StringIO.new
-
2
app_logo_file << "app_logos/logo_"
-
-
2
then: 2
else: 0
app_logo_file << (app == :manager ? "loopos" : app.to_s)
-
-
2
then: 0
else: 2
app_logo_file << (expanded ? "_expanded.svg" : ".svg")
-
-
2
app_logo_file.string
-
end
-
-
1
class << self
-
1
def apps
-
1
APPS
-
end
-
end
-
end
-
end
-
2
<div class="lui-app_logo">
-
2
<%= image_tag app_logo_file, class: "lui-app_logo_image" %>
-
</div>
-
1
module LooposUi
-
1
module Apps
-
1
class List < LoopComponent
-
1
ORDER = [:manager, :core, :submission, :validation, :hubs, :handling, :exit]
-
-
1
option :list
-
end
-
end
-
end
-
<%= render LooposUi::TokenList.new do |token_list| %>
-
<% ORDER.each do |app| %>
-
<% details = list[app] %>
-
then: 0
else: 0
<% if details.present? %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::IconTooltip.new(
-
app: app.to_s,
-
count: details[:count],
-
text: details[:tooltip]
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<div class="loopui-assign-component">
-
<div class="loopui-assign-component__left">
-
<%= left_section %>
-
</div>
-
<div class="loopui-assign-component__right">
-
<%= right_section %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Assign
-
1
class AssignComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
renders_one :left_section
-
1
renders_one :right_section
-
end
-
end
-
end
-
<div class="loopui-assign-section">
-
<div class="loopui-assign-section__top-wrapper">
-
<%= render LooposUi::TitleDescription.new(title: title, size: "small") %>
-
<%# FIXME: fix cancel btn considering the cancel and save logics %>
-
then: 0
else: 0
<% if @selected_resource.present? %>
-
<a href="" data-turbo-frame="tabs_content"><i class="fa-regular fa-times"></i></a>
-
<% end %>
-
</div>
-
<div class="loopui-assign-section__wrapper" data-controller="assign-search">
-
<% if @assign_mode %>
-
then: 0
<%# submit form wrapping %>
-
then: 0
else: 0
<% if !@single_side %>
-
<div class="loopui-assign-section__elements-section">
-
then: 0
<% if @assigned.present? %>
-
<% @assigned.each do |resource| %>
-
<%= render LooposUi::Assign::Elements.new(main_resource: @main_resource, selected_resource: @selected_resource, resource: resource, edit_mode: @edit_mode, assign_mode: @assign_mode, edit_path: @edit_path, unassign_path: @unassign_path, card: @card, assigned: true, method: @method, turbo_frame_prefix: @turbo_frame_prefix)%>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Assign::Empty.new(title: "No assigned resources") %>
-
<% end %>
-
</div>
-
<div class="loopui-assign-section__separator">
-
<hr class="loopui-assign-section__line">
-
<span class="loopui-assign-section__separator-text"> Add more <%= title %></span>
-
<hr class="loopui-assign-section__line">
-
</div>
-
<% end %>
-
<%# search form wrapping %>
-
then: 0
else: 0
<% if @search_path.present? %>
-
then: 0
else: 0
<%= form_tag @search_path&.call(@main_resource), method: @search_method, data:{ action: "submit->assign-search#setSectionLoading"} do %>
-
then: 0
else: 0
<%= hidden_field_tag :selected_resource_id, @selected_resource&.id %>
-
<%= hidden_field_tag :turbo_frame_prefix, @turbo_frame_prefix %>
-
-
then: 0
else: 0
<% if @single_side %>
-
<%= hidden_field_tag :format, :turbo_stream %>
-
<% end %>
-
-
<%= render LooposUi::Filter::SearchComponent.new(param: @search_param, search_query: @search_query, placeholder: t(".search")) %>
-
<% end %>
-
<% end %>
-
<div class="loopui-assign-section__elements-section" data-assign-search-target="result">
-
then: 0
<% if single_side %>
-
then: 0
<% if @assigned.present? || @unassigned.present? %>
-
then: 0
else: 0
<% resources = @assigned.present? ? @assigned : @unassigned %>
-
<% assigned_status = @assigned.present? %>
-
<% resources.each do |resource| %>
-
<%= render LooposUi::Assign::Elements.new(
-
main_resource: @main_resource,
-
selected_resource: @selected_resource,
-
resource: resource,
-
edit_mode: false,
-
assign_mode: @assign_mode,
-
edit_path: @edit_path,
-
then: 0
else: 0
assign_path: assigned_status ? nil : @assign_path,
-
then: 0
else: 0
unassign_path: assigned_status ? @unassign_path : nil,
-
card: @card,
-
assigned: true,
-
method: @method,
-
turbo_frame_prefix: @turbo_frame_prefix,
-
) %>
-
<% end %>
-
else: 0
<% else %>
-
then: 0
else: 0
<%= render LooposUi::Assign::Empty.new(title: @assigned.present? ? "No assigned resources" : "No more resources to assign") %>
-
<% end %>
-
else: 0
<% else %>
-
then: 0
<% if @unassigned.present? %>
-
<% resources = @unassigned %>
-
<% assigned_status = false %>
-
<% resources.each do |resource| %>
-
<%= render LooposUi::Assign::Elements.new(
-
main_resource: @main_resource,
-
selected_resource: @selected_resource,
-
resource: resource,
-
edit_mode: false,
-
assign_mode: @assign_mode,
-
edit_path: @edit_path,
-
then: 0
else: 0
assign_path: assigned_status ? nil : @assign_path,
-
then: 0
else: 0
unassign_path: assigned_status ? @unassign_path : nil,
-
card: @card,
-
assigned: true,
-
method: @method,
-
turbo_frame_prefix: @turbo_frame_prefix,
-
) %>
-
<% end %>
-
else: 0
<% else %>
-
then: 0
else: 0
<%= render LooposUi::Assign::Empty.new(title: @assigned.present? ? "No assigned resources" : "No more resources to assign") %>
-
<% end %>
-
<% end %>
-
</div>
-
else: 0
<% else %>
-
then: 0
else: 0
<% if @search_path.present? %>
-
<%= form_tag @search_path.call(@main_resource), method: @search_method, data:{ action: "submit->assign-search#setSectionLoading"} do %>
-
then: 0
else: 0
<%= hidden_field_tag :selected_resource_id, @selected_resource&.id %>
-
then: 0
else: 0
<% if @single_side %>
-
<%= hidden_field_tag :format, :turbo_stream %>
-
<% end %>
-
-
<%= render LooposUi::Filter::SearchComponent.new(param: @search_param, search_query: @search_query, placeholder: t(".search")) %>
-
<% end %>
-
<% end %>
-
<div class="loopui-assign-section__elements-section" data-assign-search-target="result">
-
then: 0
<% if @assigned.present? %>
-
<% @assigned.each do |resource| %>
-
<%= render LooposUi::Assign::Elements.new(main_resource: @main_resource, selected_resource: @selected_resource, resource: resource, edit_mode: @edit_mode, assign_mode: @assign_mode, edit_path: @edit_path, assign_path: @assign_path, unassign_path: @unassign_path, card: @card, assigned: true, method: @method, turbo_frame_prefix: @turbo_frame_prefix)%>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Assign::Empty.new(title: "No assigned resources") %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Assign
-
1
class AssignSection < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
renders_one :assigned_area
-
1
renders_one :unassigned_area
-
-
1
attr_reader :card,
-
:main_resource,
-
:selected_resource ,
-
:title,
-
:assigned,
-
:unassigned,
-
:edit_mode,
-
:assign_mode,
-
:edit_path,
-
:assign_path,
-
:unassign_path,
-
:search_path,
-
:single_side,
-
:method,
-
:search_param,
-
:search_query,
-
:search_method,
-
:turbo_frame_prefix
-
-
1
def initialize(card: "", main_resource: nil, selected_resource: nil, title: "", assigned: nil, unassigned: nil,
-
edit_mode: false, assign_mode: false, edit_path: nil, assign_path: nil, unassign_path: nil, search_path: nil, single_side: false, method: nil, search_param: "q[search]", search_query: "", search_method: :post, turbo_frame_prefix: "")
-
@card = card
-
@main_resource = main_resource
-
@selected_resource = selected_resource
-
@title = title
-
@assigned = assigned
-
@unassigned = unassigned
-
@edit_mode = edit_mode
-
@assign_mode = assign_mode
-
@edit_path = edit_path
-
@assign_path = assign_path
-
@unassign_path = unassign_path
-
@search_path = search_path
-
@single_side = single_side
-
@method = method
-
@search_param = search_param
-
@search_query = search_query
-
@search_method = search_method
-
@turbo_frame_prefix = turbo_frame_prefix
-
# @args = args
-
end
-
end
-
end
-
end
-
<%
-
then: 0
card_partial = if @card.present?
-
render partial: @card, locals: { resource: @resource, assigned: @assigned , main_resource: @main_resource}
-
else: 0
else
-
"Missing card partial for: #{resource.class.name}"
-
end
-
%>
-
<div class="relative">
-
then: 0
<% if @edit_mode %>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<%= link_to @edit_path.call(selected_resource_id: @selected_resource&.id , resource_id: @resource.id, turbo_frame: @turbo_frame_prefix.present? ? "#{@turbo_frame_prefix}__assign__right" : "assign__right", turbo_frame_prefix: @turbo_frame_prefix), method: :post, data: { turbo_frame: @turbo_frame_prefix.present? ? "#{@turbo_frame_prefix}__assign__right" : "assign__right", controller: "assign" }, class: "group", id: "your-link-id" do %>
-
<%= card_partial %>
-
<% end %>
-
else: 0
<% else %>
-
<%= card_partial %>
-
<% end %>
-
else: 0
<% if @assign_mode && !@resource.try(:inherited?) && !@resource.try(:unassignable?) %>
-
then: 0
<%
-
assign = @assign_path.present?
-
then: 0
else: 0
change_path = assign ? @assign_path : @unassign_path
-
then: 0
else: 0
icon = assign ? "fa-plus" : "fa-minus"
-
then: 0
else: 0
then: 0
else: 0
method = @method.present? ? @method : (assign ? :put : :delete)
-
%>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<%= link_to change_path.call(selected_resource_id: @selected_resource&.id , resource_id: @resource.id, turbo_frame: @turbo_frame_prefix.present? ? "#{@turbo_frame_prefix}__assign__left" : "assign__left", turbo_frame_prefix: @turbo_frame_prefix), data:{ turbo_frame: @turbo_frame_prefix.present? ? "#{@turbo_frame_prefix}__assign__left" : "assign__left" }, method: method, class:"loopui-center-buttn-temp" do %>
-
<div class="w-8 h-8 p-3 bg-gray-50 rounded-md border border-zinc-200 justify-center items-center gap-2 inline-flex">
-
<i class="fa-regular <%= icon %> text-gray-900"></i>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Assign
-
1
class Elements < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_reader :card,
-
:main_resource,
-
:selected_resource,
-
:resource,
-
:edit_mode,
-
:edit_path,
-
:assign_mode,
-
:assign_path,
-
:unassign_path,
-
:assigned,
-
:method,
-
:turbo_frame_prefix
-
-
1
def initialize(card: "", main_resource: nil, selected_resource: nil, resource: nil,edited_resource: nil,
-
edit_mode: false, edit_path: nil, assign_mode: false, assign_path: nil, unassign_path: nil, assigned: false, method: nil, turbo_frame_prefix: "")
-
@card = card
-
@main_resource = main_resource
-
@selected_resource = selected_resource
-
@resource = resource
-
@edited_resource = edited_resource
-
@edit_mode = edit_mode
-
@assign_mode = assign_mode
-
@edit_path = edit_path
-
@assign_path = assign_path
-
@unassign_path = unassign_path
-
@assigned = assigned
-
@method = method
-
@turbo_frame_prefix = turbo_frame_prefix
-
end
-
end
-
end
-
end
-
<div class="w-full h-[72px] p-2 bg-gray-50 rounded border border-gray-50 justify-center items-center gap-2 inline-flex">
-
<div class="grow shrink basis-0 flex-col justify-start items-center gap-2 inline-flex">
-
<div class="self-stretch h-[17px] flex-col justify-start items-center gap-2 flex">
-
<div class="text-neutral-800 copy-14"><%= title %></div>
-
</div>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Assign
-
1
class Empty < ViewComponent::Base
-
1
attr_reader :title
-
-
1
def initialize(title: "")
-
@title = title
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Assign
-
1
class Update < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_reader :card,
-
:update_wrapper,
-
:main_resource,
-
:selected_resource,
-
:title,
-
:assigned,
-
:unassigned,
-
:edit_mode,
-
:edit_path,
-
:assign_mode,
-
:assign_path,
-
:unassign_path,
-
:change_turbo_stream,
-
:search_path,
-
:single_side,
-
:method,
-
:search_param,
-
:search_query,
-
:search_method,
-
:turbo_frame_prefix
-
-
1
def initialize(card: "",update_wrapper:, main_resource:, selected_resource: nil, title: "", assigned: nil,
-
unassigned: nil, edit_mode: false, edit_path: nil, assign_mode: false, assign_path: nil, unassign_path: nil, change_turbo_stream: false, search_path: nil, single_side: false, method: nil, search_param: "q[search]", search_query: "", search_method: :post, turbo_frame_prefix: "")
-
@card = card
-
@change_turbo_stream = change_turbo_stream
-
@update_wrapper = update_wrapper
-
@main_resource = main_resource
-
@selected_resource = selected_resource
-
@title = title
-
@assigned = assigned
-
@unassigned = unassigned
-
@edit_mode = edit_mode
-
@edit_path = edit_path
-
@assign_mode = assign_mode
-
@assign_path = assign_path
-
@unassign_path = unassign_path
-
@search_path = search_path
-
@single_side = single_side
-
@method = method
-
@search_param = search_param
-
@search_query = search_query
-
@search_method = search_method
-
@turbo_frame_prefix = turbo_frame_prefix
-
end
-
end
-
end
-
end
-
<% section =
-
LooposUi::Assign::AssignSection.new(
-
card: @card,
-
main_resource: @main_resource,
-
selected_resource: @selected_resource,
-
title: @title,
-
assigned: @assigned,
-
unassigned: @unassigned,
-
edit_path: @edit_path,
-
edit_mode: @edit_mode,
-
assign_mode: @assign_mode,
-
assign_path: @assign_path,
-
unassign_path: @unassign_path,
-
search_path: @search_path,
-
single_side: @single_side,
-
method: @method,
-
search_param: @search_param,
-
search_query: @search_query,
-
search_method: @search_method,
-
turbo_frame_prefix: @turbo_frame_prefix,
-
)
-
%>
-
then: 0
else: 0
<%= turbo_stream.update @turbo_frame_prefix.present? ? "#{@turbo_frame_prefix}__#{@update_wrapper}" : @update_wrapper do %>
-
<%= render section %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class AssociationOverlay < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
renders_one :header, "LooposUi::AssociationOverlay::Header"
-
-
1
renders_one :selected_container
-
1
renders_one :results_container
-
-
1
renders_one :new_item
-
-
1
option :id, optional: true, default: -> { "association-overlay-#{Random.hex(10)}" }
-
1
option :hidden, default: -> { false }
-
1
option :tag_options, default: -> { {} }
-
1
option :draggable, default: -> { false }
-
1
option :can_search, default: -> { true }
-
1
option :show_selected, default: -> { true }
-
1
option :show_results, default: -> { true }
-
-
1
class SelectedContainer < LoopComponent
-
1
option :id, optional: true
-
-
1
renders_many :selected_items
-
end
-
-
1
class ResultsContainer < LoopComponent
-
1
option :id, optional: true
-
1
option :show_results, default: -> { true }
-
-
1
renders_many :results
-
1
renders_one :new_item
-
-
1
def classes
-
join_classes(
-
"lui-association-overlay__results",
-
then: 0
else: 0
show_results ? "overflow-scroll" : "overflow-hidden",
-
)
-
end
-
end
-
-
1
class Header < LoopComponent
-
1
include FaviconAware
-
-
1
option :title, optional: true
-
1
option :icon, optional: true
-
-
1
renders_many :action_buttons, LooposUi::Button
-
-
1
def initialize(...)
-
super(...)
-
-
@icon = faviconize(icon)
-
end
-
end
-
end
-
end
-
<%= tag.div(id: id, class: "lui-association-overlay", **tag_options) do %>
-
then: 0
else: 0
<% if header? %>
-
<%= header %>
-
<div class="lui-association-overlay__divider"></div>
-
<% end %>
-
then: 0
else: 0
<%= selected_container if show_selected %>
-
then: 0
else: 0
<%= tag.div(class: "lui-association-overlay__divider") if show_selected %>
-
then: 0
else: 0
<% if can_search %>
-
<div class="lui-association-overlay__search">
-
<i class="fa-regular fa-search text-[8px] font-bold"></i>
-
<input type="text" placeholder="<%= t('.search_or_create') %>" data-model-association-overlay-target="input" autocomplete="off">
-
</div>
-
<div class="lui-association-overlay__divider"></div>
-
<% end %>
-
<%= results_container %>
-
<% end %>
-
<div class="lui-association-overlay__header">
-
then: 0
else: 0
<%= tag.i(class: icon) if icon.present?%>
-
then: 0
else: 0
<%= title if title.present? %>
-
</div>
-
<%= tag.div( class: classes, id: id,
-
data: { "model-association-overlay-target": "resultsContainer"} ) do %>
-
then: 0
else: 0
<% results.each do |result| %>
-
<%= result %>
-
<% end if show_results %>
-
then: 0
else: 0
<%= new_item if new_item? %>
-
then: 0
else: 0
<%= tag.div(
-
t('.no_results_found'),
-
class: "hidden lui-association-overlay__empty-search",
-
data: { model_association_overlay_target: "emptySearch" }
-
) if show_results %>
-
<% end %>
-
<%= tag.div(class: "lui-association-overlay__selected_list") do %>
-
<div class="flex items-center gap-2 flex-wrap" id="<%= id %>" data-controller="drag">
-
<% selected_items.each do |item| %>
-
<%= item %>
-
<% end %>
-
</div>
-
<% end %>
-
<%= tag.div data: data_attributes do %>
-
<%= header %>
-
<div data-async-select-component-target='dropdown' data-dropdown-toggle-target="<%= dropdown_id %>" class="loopui-async-select__tag">
-
<%= toggler %>
-
</div>
-
<div id="<%= dropdown_id %>" class="hidden loopui-async-select__dropdown">
-
<div class="p-3">
-
<label for="input-group-search" class="loopui-async-select__label">Search</label>
-
<div class="relative">
-
<div class="loopui-async-select__search-icon">
-
<i class="fa-regular fa-magnifying-glass"></i>
-
</div>
-
<input
-
id="input-group-search"
-
type="text"
-
data-action='keydown->async-select-component#filterKeyDown input->async-select-component#filterInput'
-
class="loopui-async-select__search-input"
-
then: 0
else: 0
placeholder="<%= @creatable ? "Search or Create..." : "Search..." %>">
-
</div>
-
</div>
-
<div class="tags-filter-container loopui-async-select__filter-container" aria-labelledby="dropdownSearchButton">
-
<%= turbo_frame_tag filter_wrapper_id, data:{ "async-select-component-target": "dropdownTurbo"} do %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
-
-
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module AsyncSelect
-
1
class AsyncSelectComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :header
-
1
renders_one :toggler
-
1
renders_many :options, AsyncSelectOptionComponent
-
-
1
attr_accessor :object, :filter_wrapper_id, :param_key
-
-
1
def initialize(filter_path:,
-
assign_path:,
-
data_attributes: {},
-
options: [],
-
object: nil,
-
filter_wrapper_id: nil,
-
param_key: "name",
-
creatable: false)
-
@filter_path = filter_path
-
@assign_path = assign_path
-
@data_attributes = data_attributes
-
@object = object
-
@filter_wrapper_id = filter_wrapper_id
-
@param_key = param_key
-
@creatable = creatable
-
end
-
-
1
def dropdown_id
-
# Maybe we can unique property for the select component, random for now
-
@dropdown_id ||= SecureRandom.hex(10)
-
end
-
-
1
def data_attributes
-
{
-
controller: "async-select-component",
-
"async-select-component-filter-path-value": @filter_path,
-
"async-select-component-assign-path-value": @assign_path,
-
"async-select-component-param-key-value": @param_key,
-
}.merge!(@data_attributes)
-
end
-
end
-
end
-
end
-
<%= turbo_frame_tag "filter-#{object_identifier}" do %>
-
<%= tag.div( class:"w-full", data: { action: "click->async-select-component#labelClick", payload: data_payload, value: submit_value }.merge(label_data)) do %>
-
then: 0
else: 0
<div <%= data_attributes.map { |key, value| "data-#{key}=#{value}" }.join(' ') if data_attributes.present? %>
-
class="filter-options-container async-select__options-wrapper">
-
then: 0
<% if label_component? %>
-
<span class="loopui-async-select__options-text"><%= label_component %></span>
-
else: 0
<% else %>
-
then: 0
else: 0
<%= tag.label(input_id, class: "tags-label cursor-pointer tags-label-#{object_identifier} loopui-async-select__options-text", style: color.present? ? "color: #{color}" : '') do %>
-
<%= label %>
-
<% end %>
-
<% end %>
-
then: 0
<% if persisted? %>
-
then: 0
else: 0
<% if edit_component.present? %>
-
<%= edit_component %>
-
<% end %>
-
<% else %>
-
else: 0
<%# TODO: translations %>
-
<span class='loopui-async-select__create-new'>create new</span>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module AsyncSelect
-
1
class AsyncSelectOptionComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :label_component
-
1
renders_one :edit_component
-
-
1
attr_accessor :label, :object, :data_payload, :data_attributes, :label_data, :submit_value
-
-
1
def initialize(label:, object: nil, data_payload: {}, data_attributes: nil, label_data: {}, submit_value: nil)
-
@label = label
-
@object = object
-
@data_payload = data_payload
-
then: 0
else: 0
@data_attributes = data_attributes.respond_to?(:dig) ? data_attributes.compact_blank : {}
-
then: 0
else: 0
@label_data = label_data.respond_to?(:dig) ? label_data.compact_blank : {}
-
@submit_value = submit_value || label
-
end
-
-
1
def input_id
-
"input_label_#{object_identifier}"
-
end
-
-
1
def object_identifier
-
then: 0
else: 0
object.respond_to?(:dom_id) ? dom_id(object) : object.to_s
-
end
-
-
1
def theme
-
then: 0
else: 0
@object.respond_to?(:theme) ? @object.theme : nil
-
end
-
-
1
def color
-
then: 0
else: 0
return theme.text_color if theme.present? && object.persisted?
-
-
then: 0
else: 0
@color || (@object.respond_to?(:color) ? @object.color : "black")
-
end
-
-
1
def persisted?
-
then: 0
else: 0
@object.respond_to?(:persisted?) ? @object.persisted? : true
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module AsyncSelect
-
1
class FilterComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_accessor :resource, :filtered_resource, :filter_wrapper_prefix
-
-
1
def initialize(resource:, filtered_resource:, filter_wrapper_prefix:)
-
@resource = resource
-
@filtered_resource = filtered_resource
-
@filter_wrapper_prefix = filter_wrapper_prefix
-
end
-
end
-
end
-
end
-
<%= turbo_stream.update "#{@filter_wrapper_prefix}#{dom_id(@resource)}" do %>
-
then: 0
<% if @filtered_resource.present? %>
-
<% @filtered_resource.each do |bd| %>
-
<%= render LooposUi::AsyncSelect::AsyncSelectOptionComponent.new(label: bd.name, object: bd, submit_value: bd.id) %>
-
<% end %>
-
else: 0
<% else %>
-
No results found
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module AsyncSelect
-
1
class UpdateListComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_accessor :update_wrapper, :partial_content
-
-
1
def initialize(update_wrapper:, partial_content:)
-
@update_wrapper = update_wrapper
-
@partial_content = partial_content
-
end
-
end
-
end
-
end
-
<%= turbo_stream.update @update_wrapper do %>
-
<%= raw @partial_content %>
-
<% end %>
-
# frozen_string_literal: true
-
-
# TODO: update this for UI 2.0 Foundations
-
1
module LooposUi
-
1
class Avatar < LoopComponent
-
1
SIZES = [:xs, :small, :medium, :large, :xl]
-
1
TYPES = [:circle, :square]
-
-
1
option :type, Types::Symbol.enum(*TYPES), default: -> { :circle }
-
1
mod :type
-
1
option :size, Types::Symbol.enum(*SIZES), default: -> { :medium }
-
1
mod :size
-
1
option :image_url, Types::String | Types::Symbol.enum(:default)
-
-
1
class << self
-
# Some factory methods to make it easier to create avatars
-
# For now they only set the correct type, but later they will be able to extract the image_url from the user or partner object
-
1
def user(user: nil, **options)
-
new(type: :circle, **options)
-
end
-
-
1
def partner(partner: nil, **options)
-
new(type: :square, **options)
-
end
-
end
-
-
1
def before_render
-
then: 0
else: 0
@image_url = default_image if image_url == :default
-
end
-
-
1
private
-
-
# TODO: set this to the correct path
-
1
def default_image
-
case type
-
when: 0
when :circle
-
helpers.image_url("loopos_ui/avatar/user-avatar.png")
-
else: 0
else # :square
-
helpers.image_url("loopos_ui/avatar/partner-avatar.png")
-
end
-
end
-
end
-
end
-
<%= tag.div class: classes do %>
-
<%= image_tag image_url, alt: "Avatar", class: "lui-avatar__image" %>
-
<% end %>
-
-
1
module LooposUi
-
1
class BasicRadioButton < LoopComponent
-
1
option :value, Types::Coercible::String
-
1
option :name, Types::Coercible::String
-
1
option :text, Types::String
-
1
option :checked, Types::Bool, default: -> { false }
-
1
option :disabled, Types::Bool, default: -> { false }
-
1
option :description, Types::String, default: -> { nil }
-
1
option :form, Types::Coercible::String, optional: true
-
-
1
use_extra_options
-
-
1
private
-
-
1
def option_id
-
"#{name}-#{value}-#{text.parameterize}"
-
end
-
end
-
end
-
<%= tag.div class: classes, **extra_options do %>
-
<%= tag.span class: "lui-basic_radio_button__option" do %>
-
<%= tag.input type: "radio", id: option_id, name: name, value: value, checked: checked, disabled: disabled, form: form %>
-
<%= tag.label text, for: option_id, class: "copy-14 text-content"%>
-
<% end %>
-
then: 0
else: 0
<% if description.present? %>
-
<div class="lui-basic_radio_button__description">
-
<%= tag.span description, class: "copy-14 text-content-secondary" %>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class BottomBar < LoopComponent
-
1
renders_many :mini_apps, LooposUi::MiniApp
-
1
renders_one :action_buttons, LooposUi::ActionButtons
-
end
-
end
-
2
<div>
-
2
<% mini_apps.each do |mini_app| %>
-
10
<div class="draggable w-fit h-fit absolute" data-controller="miniapp-drag">
-
10
<%= mini_app %>
-
</div>
-
<% end %>
-
2
</div>
-
<div class="lui-bottom_bar">
-
2
<%= action_buttons %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class BreadcrumbList < ViewComponent::Base
-
1
renders_many :breadcrumbs, "Breadcrumb"
-
-
1
def before_render
-
breadcrumbs.last && breadcrumbs.last.last = true
-
end
-
-
1
class Breadcrumb < ViewComponent::Base
-
1
attr_writer :last
-
-
1
def initialize(href: nil, last: false, data: {})
-
@href = href
-
@last = last
-
@data = data
-
end
-
-
1
def call
-
then: 0
if @last
-
tag.span(content, class: "lui-breadcrumb_list__breadcrumb")
-
else: 0
else
-
link_to(
-
content,
-
@href,
-
class: "lui-breadcrumb_list__breadcrumb",
-
data: { turbo_action: "advance" }.merge(@data),
-
)
-
end
-
end
-
end
-
end
-
end
-
<div class="lui-breadcrumb_list">
-
<% breadcrumbs.each_with_index do |b, index| %>
-
<div class="lui-breadcrumb_list__breadcrumb-container">
-
<%= b %>
-
then: 0
else: 0
<% if index < breadcrumbs.length - 1 %>
-
<span class="lui-breadcrumb_list__breadcrumb-separator">/</span>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Button < LoopComponent
-
1
include Presets
-
1
include LooposUi::FaviconAware
-
-
1
SIZES = [
-
:default, :small, :tiny,
-
]
-
-
1
NEW_SIZES = [
-
:large, :medium, :small, # We could make this compatible with the old sizes, but there's a collision :(
-
]
-
-
1
TYPES = [
-
:primary,
-
:secondary,
-
:tertiary,
-
]
-
-
# Deprecated, will be removed
-
1
KINDS = [
-
:app,
-
:neutral,
-
:success,
-
:danger,
-
]
-
-
1
APP_ICONS = [
-
"core",
-
"manager",
-
"submission",
-
"hubs",
-
"validation",
-
"handling",
-
"exits",
-
"impact",
-
"submission_extra",
-
]
-
-
1
renders_one :status_dot, ->(kind:, **kwargs) {
-
LooposUi::StatusDot.new(kind: kind, **kwargs)
-
}
-
-
1
renders_one :counter, ->(count:, **kwargs) {
-
LooposUi::Counter.new(count: count, **kwargs)
-
}
-
-
1
renders_one :leading_icon, ->(icon:, **_kwargs) {
-
160
then: 0
if APP_ICONS.include?(icon.to_s)
-
else: 160
icon
-
160
then: 0
elsif MIcon.icon?(icon)
-
LooposUi::MIcon.new(icon, tag: :i, size: icon_size)
-
else
-
else: 160
# To temprarily support fa- icons but keep the new sizes
-
160
content_tag(:div, class: "flex items-center justify-center", style: "width: #{icon_size}px; height: #{icon_size}px;") do
-
160
content_tag(:i, "", class: "lui-button__icon lui-button__icon--#{@size} #{icon}", data: { "lui--button-target": "leadingIcon" })
-
end
-
end
-
}
-
-
1
renders_one :trailing_icon, ->(icon:, **_kwargs) {
-
84
then: 0
if APP_ICONS.include?(icon.to_s)
-
else: 84
icon
-
84
then: 0
elsif MIcon.icon?(icon)
-
LooposUi::MIcon.new(icon, tag: :i, size: icon_size)
-
else
-
else: 84
# To temprarily support fa- icons but keep the new sizes
-
84
content_tag(:div, class: "flex items-center justify-center", style: "width: #{icon_size}px; height: #{icon_size}px;") do
-
84
content_tag(:i, "", class: "lui-button__icon lui-button__icon--#{@size} #{icon}")
-
end
-
end
-
}
-
-
1
def icon_size
-
488
case @size
-
when: 140
when :default
-
140
16
-
when: 172
when :small
-
172
14
-
else: 176
else
-
176
12
-
end
-
end
-
-
1
option :primary_color, Types::Coercible::String, optional: true
-
1
option :hover_color, Types::Coercible::String, optional: true
-
1
option :text_color, Types::Coercible::String, default: -> { LooposUi::Colors.find("general-global-white") }
-
1
option :load_on_click, Types::Bool, default: -> { true }
-
-
1
attr_reader :text, :tag, :kind
-
-
1
def initialize(
-
kind: :app,
-
type: :primary,
-
app: nil,
-
size: :default,
-
href: nil,
-
tag: nil,
-
tag_options: {},
-
text: nil,
-
app_icon_size: :small,
-
leading_icon: nil,
-
icon: nil,
-
trailing_icon: nil,
-
disabled: false,
-
tooltip_text: nil,
-
active: true,
-
full: false,
-
status_dot: nil,
-
load_on_click: true
-
)
-
168
else: 168
then: 0
raise ArgumentError, "Invalid size: #{size}" unless SIZES.include?(size)
-
168
else: 168
then: 0
raise ArgumentError, "Invalid type: #{type}" unless TYPES.include?(type)
-
168
else: 168
then: 0
raise ArgumentError, "Invalid kind: #{kind}" unless KINDS.include?(kind)
-
-
# Deprecated, will be removed
-
168
then: 18
else: 150
@app = kind == :app ? (app || LooposUi.config.app_type) : nil
-
168
@kind = kind
-
-
-
168
@text = text
-
168
@size = size
-
168
@type = type
-
-
168
@tooltip_text = initial_tooltip_text(tag_options: tag_options, tooltip_text: tooltip_text)
-
-
168
@app_icon_size = app_icon_size
-
-
168
@disabled = disabled
-
168
@active = active
-
168
@full = full
-
-
168
@href = href
-
168
then: 76
else: 92
@tag = tag || (href.present? && !disabled? ? :a : :button)
-
168
@tag_options = tag_options
-
168
@load_on_click = load_on_click
-
-
168
then: 0
@leading_icon = if (leading_icon && APP_ICONS.include?(leading_icon.to_s)) || MIcon.icon?(leading_icon)
-
leading_icon
-
else: 168
else
-
168
faviconize(leading_icon || icon)
-
end
-
168
then: 0
@trailing_icon = if (trailing_icon && APP_ICONS.include?(trailing_icon.to_s)) || MIcon.icon?(trailing_icon)
-
trailing_icon
-
else: 168
else
-
168
faviconize(trailing_icon)
-
end
-
-
# Validate that text is present unless there's at least one icon
-
168
else: 168
then: 0
unless @text.present? || @leading_icon.present? || @trailing_icon.present?
-
raise ArgumentError, "Text must be provided unless leading_icon or trailing_icon is present"
-
end
-
-
168
@status_dot = status_dot
-
end
-
-
1
def before_render
-
166
then: 84
else: 82
with_trailing_icon(icon: @trailing_icon) if @trailing_icon
-
166
then: 160
else: 6
with_leading_icon(icon: @leading_icon) if @leading_icon
-
166
then: 0
else: 166
with_status_dot(kind: @status_dot) if @status_dot
-
end
-
-
1
def classes
-
[
-
166
then: 40
else: 126
icon_only? ? "lui-button--icon-only" : nil,
-
theme_class,
-
app_classes,
-
size_classes,
-
disabled_classes,
-
specific_app_classes,
-
active_classes,
-
full_classes,
-
].compact.join(" ")
-
end
-
-
1
def tag_options
-
166
then: 74
options = if @tag == :a && !disabled?
-
74
deep_merge_args(
-
{
-
href: @href,
-
74
then: 74
else: 0
data: {}.merge(@load_on_click ? { action: "lui--button#startLoading" } : {}),
-
},
-
@tag_options,
-
)
-
else: 92
else
-
92
@tag_options
-
end
-
-
166
then: 18
else: 148
if load_on_click_for_button?(options)
-
18
options = deep_merge_args(options, { data: { action: "lui--button#startLoading" } })
-
end
-
-
166
then: 0
else: 166
options.merge!(disabled: true) if disabled?
-
-
166
deep_merge_args(
-
{
-
data: {
-
controller: "lui--button",
-
},
-
},
-
options,
-
)
-
end
-
-
# TODO: document
-
1
def disabled?
-
482
@disabled
-
end
-
-
1
def active?
-
166
@active
-
end
-
-
1
def full?
-
332
@full
-
end
-
-
1
private
-
-
1
def load_on_click_for_button?(options)
-
166
else: 166
then: 0
return false unless @load_on_click
-
166
else: 92
then: 74
return false unless @tag == :button
-
-
92
then: 36
else: 56
options[:form].present? || options[:type]&.to_sym == :submit
-
end
-
-
1
def initial_tooltip_text(tag_options:, tooltip_text:)
-
168
then: 50
else: 118
then: 0
else: 168
then: 0
else: 168
else: 0
then: 168
return tooltip_text unless tag_options[:data]&.dig(:controller)&.to_s&.include?("lui--button-tooltip-toggle")
-
-
toggled = tag_options[:data][:"lui--button-tooltip-toggle-toggled-value"]
-
then: 0
if toggled
-
tag_options[:data][:"lui--button-tooltip-toggle-toggled-text-value"]
-
else: 0
else
-
tag_options[:data][:"lui--button-tooltip-toggle-untoggled-text-value"]
-
end
-
end
-
-
1
def icon_only?
-
166
only_one_icon = [@leading_icon.present?, @trailing_icon.present?].count(true) == 1
-
166
no_text = @text.blank?
-
166
no_status_dot = @status_dot.blank?
-
166
only_one_icon && no_text && no_status_dot
-
end
-
-
1
def specific_app_classes
-
166
then: 18
else: 148
"lui-button--neutral--#{@type}" if @app.present?
-
end
-
-
1
def app_classes
-
166
else: 18
then: 148
"lui-button--neutral--#{@type}" unless @app.present?
-
end
-
-
1
def theme_class
-
166
else: 28
then: 138
return unless @type == :primary
-
28
else: 0
then: 28
return unless LooposUi::Current.theme_context.present?
-
-
"lui-button--themed"
-
end
-
-
1
def size_classes
-
166
"lui-button--size-#{@size}"
-
end
-
-
1
def disabled_classes
-
166
then: 0
else: 166
"lui-button--disabled" if disabled?
-
end
-
-
1
def active_classes
-
166
else: 166
then: 0
"lui-button--inactive" unless active?
-
end
-
-
1
def full_classes
-
332
then: 0
else: 332
full? ? "w-full" : "w-fit"
-
end
-
end
-
end
-
332
<%= content_tag(tag, {class: "lui-button #{classes} #{full_classes} relative", **tag_options } ) do %>
-
166
then: 0
else: 166
<%= render LooposUi::Tooltip.new(title: @tooltip_text) if @tooltip_text.present? %>
-
166
<% case leading_icon.to_s %>
-
when: 0
<% when "core", "submission", "hubs", "validation", "handling", "exits", "impact", "submission_extra" %>
-
<div class="flex items-center justify-center opacity-100" data-lui--button-target="leadingIcon" style="width: <%= icon_size %>px; height: <%= icon_size %>px;">
-
then: 0
else: 0
<%= helpers.app_svg(app: leading_icon.to_s, size: defined?(@app_icon_size) ? @app_icon_size : :small) %>
-
</div>
-
else: 166
<% else %>
-
326
then: 160
else: 6
<%= content_tag(:div, class: "opacity-100 inline-flex", data: { "lui--button-target": "leadingIcon" }) do %>
-
160
<%= leading_icon %>
-
166
<% end if leading_icon? %>
-
<% end %>
-
292
then: 126
else: 40
<%= content_tag(:span, class: "lui-button__text opacity-100 inline-flex", data: { "lui--button-target": :text }) do %>
-
126
<%= text %>
-
166
<% end if text.present? %>
-
166
<% case trailing_icon.to_s %>
-
when: 0
<% when "core", "submission", "hubs", "validation", "handling", "exits", "impact" %>
-
<%= content_tag(:div, class: "opacity-100 inline-flex", data: { "lui--button-target": "trailingIcon" }) do %>
-
then: 0
else: 0
<%= helpers.app_svg(app: trailing_icon.to_s, size: defined?(@app_icon_size) ? @app_icon_size : :small) %>
-
<% end %>
-
else: 166
<% else %>
-
250
then: 84
else: 82
<%= content_tag(:div, class: "opacity-100 inline-flex", data: { "lui--button-target": "trailingIcon" }) do %>
-
84
<%= trailing_icon %>
-
166
<% end if trailing_icon? %>
-
<% end %>
-
166
then: 0
else: 166
<%= content_tag(:div, class: "opacity-100 inline-flex", data: { "lui--button-target": "statusDot" }) do %>
-
<%= status_dot %>
-
166
<% end if status_dot? %>
-
166
then: 0
else: 166
<%= content_tag(:div, class: "opacity-100 inline-flex", data: { "lui--button-target": "counter" }) do %>
-
<%= counter %>
-
166
<% end if counter? %>
-
166
<div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon">
-
166
then: 28
else: 138
<%= render LooposUi::Spinner.new(theme: @type == :primary ? :dark : :light, size: @size) %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<div class="loopos-card <%= @full_width ? 'loopos-card__full-width' : '' %>">
-
<div class="loopos-card__title-wrapper">
-
<div class="loopos-card__title">
-
<%= title %>
-
</div>
-
<div class="loopos-card__actions">
-
<%= action %>
-
</div>
-
</div>
-
then: 0
else: 0
<% if top_contents.present? %>
-
<div class="loopos-card__top-contents">
-
<% top_contents.each do |element| %>
-
<%= element %>
-
<% end %>
-
</div>
-
<% end %>
-
<div class="loopos-card__content">
-
<%= card_content %>
-
</div>
-
then: 0
else: 0
<% if footer.present? %>
-
<div class="loopos-card__footer">
-
<%= footer %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Card
-
1
class CardComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :title
-
1
renders_one :action
-
1
renders_many :top_contents
-
1
renders_one :card_content
-
1
renders_one :footer
-
-
1
def initialize(full_width: false)
-
@full_width = full_width
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Carousel < LoopComponent
-
1
option :images, Types::Array.of(Types::Coercible::String)
-
1
option :height, Types::TSize, optional: true
-
1
option :width, Types::TSize, optional: true
-
-
1
option :dots, Types::Bool, default: -> { true }
-
1
mod :with_dots, condition: -> { dots && images.count > 1 }
-
-
1
option :preview, Types::Bool, default: -> { false }
-
1
mod :with_preview, condition: -> { preview && images.count > 1 }
-
-
1
option :data, Types::Hash, default: -> { {} }
-
-
1
def size_styles
-
<<~CSS
-
height: #{height};
-
width: #{width};
-
CSS
-
end
-
-
1
def width_styles
-
<<~CSS
-
width: #{width};
-
CSS
-
end
-
-
1
def own_class
-
"#{super}_container"
-
end
-
-
1
def data
-
deep_merge_args(
-
{ controller: "lui--carousel-container" },
-
@data,
-
)
-
end
-
end
-
end
-
<%= tag.div(class: classes, data: data) do %>
-
<section
-
id="<%= random_id %>"
-
class="lui-carousel"
-
data-controller="carousel"
-
data-carousel-loop-value="true"
-
data-carousel-dots-value="true">
-
<%= tag.div(class: "lui-carousel-viewport", data: { carousel_target: :viewport }, style: size_styles) do %>
-
<%= tag.div(class: "flex aspect-square", style: size_styles ) do %>
-
<% images.each do |image| %>
-
<%= image_tag image, class: "lui-carousel-image" %>
-
<% end %>
-
<% end %>
-
<div class="lui-carousel-dots" data-lui--carousel-container-target="dots">
-
<div class="lui-carousel-dots-container" data-carousel-target="dotsContainer"></div>
-
</div>
-
<% end %>
-
</section>
-
<section
-
class="lui-carousel-preview"
-
data-lui--carousel-container-target="preview"
-
data-controller="carousel"
-
data-carousel-loop-value="false"
-
data-carousel-drag-free-value="false"
-
data-carousel-dots-value="false"
-
data-carousel-buttons-value="false"
-
data-carousel-thumbnails-value="true"
-
data-carousel-main-carousel-value="<%= random_id %>"
-
>
-
<div
-
class="overflow-hidden outline-hidden cursor-grab active:cursor-grabbing"
-
data-carousel-target="viewport"
-
style="<%= width_styles %>"
-
>
-
<div class="lui-carousel-thumbnails">
-
<% images.each do |image| %>
-
<button class="shrink-0 grow-0 basis-1/4 aspect-square relative select-none outline-hidden" data-carousel-target="thumbnailButton">
-
<div class="lui-carousel-thumbnails__image">
-
<%= image_tag image, class: "aspect-square object-cover" %>
-
</div>
-
</button>
-
<% end %>
-
</div>
-
</div>
-
</section>
-
<% end %>
-
then: 0
<% if @options["legend"].present? %>
-
<% side = case @options["legend"]
-
when: 0
when "top"
-
"gap-6 flex flex-col"
-
when: 0
when "left"
-
"gap-6 flex-wrap: wrap"
-
when: 0
when "right"
-
"gap-6 flex-wrap: wrap reverse_row"
-
else: 0
else
-
"gap-6 flex flex-col reverse_col"
-
end
-
%>
-
<div class="<%= side %> flex flex-row flex-wrap max-w-lg">
-
<div class="loopui-chart-component__legend-container__<%= @options["legend"] %>">
-
<% @data.each_with_index do |data, index| %>
-
<div class="loopui-chart-component__wrapper" >
-
<p class="loopui-chart-component__box" style="background-color:<%= @options["colors"][index] %>"></p>
-
<p class="text-sm"><%= data[0] %></p>
-
</div>
-
<% end %>
-
</div>
-
<div id="impact-chart">
-
<%= send(@chart, @data, **@options, id:@options["id"], legend:false, library: {backgroundColor: @options["backgroundColor"]}) %>
-
</div>
-
</div>
-
else: 0
<% else %>
-
then: 0
<% library_options = if @chart == "bar_chart"
-
{
-
height: @data.length * 20,
-
chartArea: {
-
top: 5,
-
bottom: 5
-
},
-
hAxis: {
-
textStyle: {
-
fontSize: 9
-
}
-
},
-
vAxis: {
-
textStyle: {
-
fontSize: 9
-
},
-
showTextEvery: 1
-
}
-
}
-
else: 0
else
-
{}
-
end %>
-
<%= send(@chart, @data, **@options, id: @options["id"], legend: false,
-
library: {
-
backgroundColor: @options["backgroundColor"],
-
}.merge(library_options)
-
) %>
-
<%end%>
-
-
-
# Chart Types
-
# [pie, line, column, bar, area, scatter]
-
# Props Dictionary
-
# [donut, legend, precision, loading, empty, xtitle, ytitle, curve, points, colors,stacked,
-
# discrete, label, prefix, suffix, library, min, max, id, width, height]
-
-
1
module LooposUi
-
1
module Charts
-
1
class ChartComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
attr_reader :chart, :data, :options
-
-
CHARTKIQ_CHART_TYPES = {
-
1
pie: "pie_chart",
-
line: "line_chart",
-
column: "column_chart",
-
bar: "bar_chart",
-
area: "area_chart",
-
scatter: "scatter_chart",
-
}
-
-
CHARTKIQ_DICTIONARY = {
-
1
donut: "donut",
-
legend: "legend",
-
precision: "precision",
-
loading: "loading",
-
empty: "empty",
-
xtitle: "xtitle",
-
ytitle: "ytitle",
-
curve: "curve",
-
points: "points",
-
colors: "colors",
-
stacked: "stacked",
-
discrete: "discrete",
-
label: "label",
-
prefix: "prefix",
-
suffix: "suffix",
-
library: "library",
-
min: "min",
-
max: "max",
-
id: "id",
-
width: "width",
-
# height: "height",
-
backgroundColor: "backgroundColor",
-
}
-
-
1
def initialize(chart: nil, data: nil, options: {})
-
@chart = translator(chart)
-
@data = data
-
@options = translator(options)
-
end
-
-
1
def translator(data)
-
# This method is to plug and play with other chart gem if necessary.
-
# To change the logic is just configure the chart types, and the dictionary for the new gem and change here.
-
then: 0
if data.is_a?(String)
-
CHARTKIQ_CHART_TYPES[data.to_sym]
-
else: 0
else
-
translated_data = {}
-
data.each do |key, value|
-
translated_key = CHARTKIQ_DICTIONARY[key.to_sym] || key
-
translated_data[translated_key] = value
-
end
-
translated_data
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Chat < LoopComponent
-
1
option :item_token
-
1
option :user_id
-
1
option :app_id
-
1
option :api_key
-
1
option :type
-
1
option :user_name
-
1
option :locale
-
1
option :style, default: -> {""}
-
end
-
end
-
<%= react_component(
-
"ItemChat", {
-
itemToken: item_token,
-
userId: user_id,
-
appId: app_id,
-
apiKey: api_key,
-
type: type,
-
userName: user_name,
-
locale: locale
-
}, {style: style}
-
) %>
-
-
1
module LooposUi
-
1
class Chip < LoopComponent
-
1
option :text, default: -> { "" }, type: Types::String
-
-
1
use_extra_options
-
end
-
end
-
<%= tag.div(class: "lui-chip", **extra_options) do %>
-
<%= text %>
-
<% end %>
-
1
module LooposUi
-
1
class ChipList < LoopComponent
-
1
renders_many :chips, LooposUi::Chip
-
-
1
use_extra_options
-
-
1
def extra_options
-
deep_merge_args(
-
{ data: { controller: "lui--blurred-scroll" } },
-
@extra_options,
-
)
-
end
-
end
-
end
-
<%= tag.div(class: "lui-chip-list", **extra_options) do %>
-
<% chips.each do |chip| %>
-
<%= chip %>
-
<% end %>
-
<% end %>
-
1
require "uri"
-
-
1
module LooposUi
-
1
class CodeEditor < LoopComponent
-
# Internal placeholder representing intentionally-empty code in inline editing.
-
1
EMPTY_CODE_PLACEHOLDER = "-"
-
-
1
option :type, type: Types::String
-
1
option :readonly, type: Types::Bool.default(true)
-
1
option :code, type: Types::String.constructor(
-
->(value) do
-
case value
-
when: 0
when String
-
value
-
when: 0
when Hash
-
value.to_json
-
else: 0
else
-
""
-
end
-
end,
-
)
-
1
option :element, optional: true
-
1
option :attribute, type: Types::String.optional, optional: true
-
1
option :show_path, type: Types::String.optional, optional: true
-
1
option :edit_path, type: Types::String.optional, optional: true
-
1
option :form_url, type: Types::String.optional, optional: true
-
1
option :input_name, type: Types::String.optional, optional: true
-
1
option :show_edit_button, type: Types::Bool, default: -> { true }
-
1
option :turbo_id, type: Types::String.optional, optional: true
-
1
option :with_turbo_wrapper, type: Types::Bool, default: -> { true }
-
1
option :hidden_fields, type: Types::Hash, default: -> { {} }
-
1
option :title, type: Types::String.optional, optional: true
-
-
1
use_extra_options
-
-
1
def inline_locals
-
else: 0
then: 0
return {} unless element.present?
-
-
inferred_attribute = attribute.presence || "code"
-
inferred_show_path = show_path.presence || current_path
-
-
locals = {
-
element: element,
-
attribute: inferred_attribute,
-
show_path: inferred_show_path,
-
edit_path: edit_path.presence || path_with_keep_inline_edit(inferred_show_path),
-
form_url: form_url.presence || inferred_show_path,
-
show_edit_button: show_edit_button,
-
turbo_id: turbo_id,
-
with_turbo_wrapper: with_turbo_wrapper,
-
hidden_fields: hidden_fields || {},
-
title: title,
-
has_title: title.present?,
-
extra_options: extra_options,
-
editor_input_name: input_name.presence || "#{element.model_name.to_s.underscore}[#{inferred_attribute}]",
-
then: 0
else: 0
edit_default_value: code == EMPTY_CODE_PLACEHOLDER ? "" : code,
-
}
-
then: 0
else: 0
locals[:input_name] = input_name if input_name.present?
-
locals
-
end
-
-
1
private
-
-
1
def current_path
-
then: 0
else: 0
view_context.request&.fullpath
-
rescue StandardError
-
nil
-
end
-
-
1
def path_with_keep_inline_edit(path)
-
then: 0
else: 0
return if path.blank?
-
-
uri = URI.parse(path)
-
query = Rack::Utils.parse_nested_query(uri.query.to_s)
-
query["keep_inline_edit"] = "true"
-
uri.query = query.to_query
-
uri.to_s
-
rescue URI::InvalidURIError
-
then: 0
else: 0
separator = path.include?("?") ? "&" : "?"
-
"#{path}#{separator}keep_inline_edit=true"
-
end
-
end
-
end
-
<%= render "loopos_ui/inline/code_editor",
-
**{ type: type, code: code, readonly: readonly, extra_options: extra_options }.merge(inline_locals)
-
%>
-
1
module LooposUi
-
1
class ContextMenu < LoopComponent
-
1
renders_many :items, "LooposUi::ContextMenu::Item"
-
1
renders_one :trigger, types: {
-
button: { renders: Button, as: :trigger_button },
-
slot: { renders: ->(&block) { capture(&block) }, as: :trigger },
-
}
-
-
1
class Item < LoopComponent
-
1
option :text, type: Types::String
-
1
option :icon, type: Types::String, default: nil, optional: true
-
1
option :disabled, type: Types::Bool, default: false, optional: true
-
1
option :data_attributes, type: Types::Hash, default: -> { {} }, optional: true
-
1
option :selected, type: Types::Bool, default: false, optional: true
-
-
1
renders_many :sub_items, Item
-
-
1
def data_attributes
-
deep_merge_args(
-
{
-
lui__context_menu_target: "item",
-
},
-
@data_attributes,
-
)
-
end
-
-
1
private
-
-
1
def icon_class
-
then: 0
else: 0
return "" if disabled
-
then: 0
else: 0
return "fa-regular fa-chevron-right" if submenu?
-
-
then: 0
if icon.present?
-
else: 0
icon
-
then: 0
elsif selected
-
"fa-regular fa-check"
-
else: 0
else
-
""
-
end
-
end
-
-
# FIXME: use mod helper instead
-
1
def item_class
-
base_classes = "lui-context-menu__item"
-
then: 0
else: 0
disabled_classes = disabled ? "lui-context-menu__item--disabled" : ""
-
then: 0
else: 0
submenu_classes = submenu? ? "lui-context-menu__item--submenu" : ""
-
-
join_classes([
-
base_classes,
-
disabled_classes,
-
submenu_classes,
-
])
-
end
-
-
1
def submenu?
-
sub_items.present? && !disabled
-
end
-
end
-
end
-
end
-
<div class="inline-flex lui-context-menu" data-controller="context-menu lui--context-menu" data-context-menu-auto-close-value="false">
-
<div class="cursor-context-menu">
-
<%= trigger %>
-
</div>
-
<dialog
-
class="lui-context-menu__dialog"
-
style="margin: 0;"
-
data-context-menu-target="menu"
-
data-controller="menu"
-
data-action="click@document->dropdown-popover#closeOnClickOutside
-
keydown.up->menu#prev keydown.down->menu#next
-
keydown.up->menu#preventScroll keydown.down->menu#preventScroll"
-
>
-
<div class="flex flex-col" role="menu">
-
<% items.each do |item| %>
-
<%= item %>
-
<% end %>
-
</div>
-
</dialog>
-
</div>
-
then: 0
<% if sub_items.present? && !disabled %>
-
<div class="flex flex-col" role="menu">
-
<div class="relative w-full focus-visible:outline-none"
-
data-controller="dropdown-popover"
-
data-dropdown-popover-nested-value="true"
-
data-dropdown-popover-auto-close-value="false"
-
data-dropdown-popover-hover-value="true"
-
data-dropdown-popover-flip-class="translate-y-full">
-
<div
-
class="focus-visible:outline-none"
-
data-dropdown-popover-target="button"
-
data-action="dropdown-popover#toggle"
-
data-menu-target="item"
-
role="menuitem">
-
<%= tag.div(class: item_class, data: data_attributes) do %>
-
<span class="lui-context-menu__item-text">
-
<%= text %>
-
</span>
-
<%= tag.span(class: "lui-context-menu__icon") do %>
-
<%= tag.i(class: icon_class) %>
-
<% end %>
-
<% end %>
-
</div>
-
<dialog
-
class="lui-context-menu__sub-dialog"
-
data-controller="menu"
-
data-dropdown-popover-target="menu"
-
data-action="click@document->dropdown-popover#closeOnClickOutside
-
keydown.up->menu#prev keydown.down->menu#next
-
keydown.up->menu#preventScroll keydown.down->menu#preventScroll"
-
>
-
<div class="flex flex-col p-2" role="menu">
-
<% sub_items.each do |sub_item| %>
-
<%= tag.div(data: sub_item.data_attributes) do %>
-
<%= sub_item %>
-
<% end %>
-
<% end %>
-
</div>
-
</dialog>
-
</div>
-
</div>
-
else: 0
<% else %>
-
<div>
-
<%= tag.div(class: item_class, data: data_attributes) do %>
-
<span class="lui-context-menu__item-text">
-
<%= text %>
-
</span>
-
<%= tag.span(class: "lui-context-menu__icon") do %>
-
<%= tag.i(class: icon_class) %>
-
<% end %>
-
-
<%= tag.i(class: "lui-context-menu__checkmark fa-regular fa-check") %>
-
<% end %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module Core
-
1
class ProductShowHeader < ViewComponent::Base
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Counter < LoopComponent
-
COLORS = {
-
# text, background
-
# TODO: use Colors.find(...)
-
1
success: [find_color("general-global-white"), find_color("general-success-800")],
-
danger: [find_color("general-global-white"), find_color("general-danger-800")],
-
warning: [find_color("general-global-white"), find_color("general-notice-800")],
-
neutral: [find_color("general-global-black"), find_color("general-gray-100")],
-
informative: [find_color("general-global-white"), find_color("general-informative-800")],
-
}
-
-
# TODO: refactor to use LoopComponent
-
# size: tinny and small, but small is the default
-
1
def initialize(count: nil, kind: :success, size: :tinny, increment: false)
-
2
@kind = kind
-
2
@count = count
-
2
@size = size
-
2
@text_color, @bg_color = COLORS[kind] || COLORS[:neutral]
-
2
@increment = increment
-
2
then: 0
else: 2
raise "No color defined for: #{kind}" if !@increment && (@text_color.nil? || @bg_color.nil?)
-
end
-
-
1
def styles
-
2
then: 0
else: 2
return if !@increment && (@text_color.nil? || @bg_color.nil?)
-
-
2
then: 2
if @kind == :neutral
-
2
<<~CSS.squish
-
color: #{@text_color};
-
background-color: #{@bg_color};
-
border: var(--Spacings-0, 1px) solid var(--General-Gray-500, #C4CAD0);
-
CSS
-
else: 0
else
-
<<~CSS.squish
-
color: #{@text_color};
-
background-color: #{@bg_color};
-
CSS
-
end
-
end
-
end
-
end
-
2
-
2
then: 0
<% if @increment %>
-
<%= tag.span(class: "lui-counter lui-counter--increment") do %>
-
<%= tag.span("+#{@count}", class: "lui-counter__text lui-counter__text--increment") %>
-
<% end %>
-
else: 2
<% else %>
-
4
<%= tag.span(class: "lui-counter lui-counter--#{@size}", style: styles) do %>
-
2
<%= tag.span(@count, class: "lui-counter__text") %>
-
<% end %>
-
<% end %>
-
2
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class CounterLabel < LooposUi::Label
-
# TODO: this should be stored in a shared colors file
-
COLORS = {
-
# text, background
-
1
manager: [find_color("apps-manager-800-primary"), find_color("apps-manager-200")],
-
core: [find_color("apps-core-800-primary"), find_color("apps-core-200")],
-
hubs: [find_color("apps-hubs-800-primary"), find_color("apps-hubs-200")],
-
submission: [find_color("apps-submission-800-primary"), find_color("apps-submission-200")],
-
validation: [find_color("apps-validation-800-primary"), find_color("apps-validation-200")],
-
handling: [find_color("apps-handling-800-primary"), find_color("apps-handling-200")],
-
default: [find_color("general-gray-900"), find_color("general-gray-200")],
-
danger: [find_color("general-danger-800"), find_color("general-danger-200")],
-
white: [find_color("general-global-black"), find_color("general-global-white")],
-
warning: [find_color("general-notice-800"), find_color("general-notice-200")],
-
informative: [find_color("general-informative-800"), find_color("general-informative-200")],
-
}
-
-
1
attr_accessor :text, :icon
-
-
# TODO: document system_args
-
1
def initialize(text: nil, icon: nil, color: nil, **system_args)
-
super(text: text, icon: icon, color: color, system_args: system_args)
-
-
@text_color, @bg_color = COLORS[color] || COLORS[LooposUi.config.app_type.to_sym]
-
-
then: 0
else: 0
raise "No color defined for: #{LooposUi.config.app_type}" if @text_color.nil? || @bg_color.nil?
-
end
-
-
1
def styles
-
else: 0
then: 0
return super unless @color == :white
-
-
then: 0
else: 0
if @color == :white
-
<<~CSS.squish
-
#{super}
-
border: 1px solid #{find_color("general-gray-200")};
-
CSS
-
end
-
end
-
end
-
end
-
<style>
-
-
/* temporary solution for gridstack problems */
-
.w-dashboard-3{
-
width: 100%;
-
max-width: 23%;
-
min-width: fit-content;
-
}
-
-
.w-dashboard-4{
-
width: 100%;
-
max-width: 31%;
-
min-width: fit-content;
-
}
-
-
.w-dashboard-6{
-
width: 100%;
-
max-width: 47%;
-
min-width: 47%;
-
-
.loopos-card {
-
width: 100%;
-
}
-
}
-
-
.w-dashboard-8{
-
width: 100%;
-
max-width: 64%;
-
min-width: fit-content;
-
}
-
-
.w-dashboard-12{
-
width:100%;
-
max-width: 98%;
-
min-width: fit-content;
-
}
-
-
</style>
-
-
<div class="grid-stack flex flex-wrap gap-8 w-full h-fit">
-
<% dashboard_cards.each do |dashboard_card| %>
-
<div class="w-dashboard-<%= dashboard_card.size %> h-fit" gs-w="<%= dashboard_card.size %>">
-
<div class="grid-stack-item-content h-fit">
-
<%= dashboard_card %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Dashboard
-
1
class DashboardComponent < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
renders_many :dashboard_cards, "LooposUi::Dashboard::DashboardCardComponent"
-
end
-
-
1
class DashboardCardComponent < LoopComponent
-
1
option :size, default: proc { 6 }
-
-
1
def call
-
content
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class DataCard < LoopComponent
-
1
option :title, optional: true
-
1
option :icon, optional: true
-
1
option :icon_rails, default: -> { true }
-
1
option :balance_value, optional: true
-
1
option :reuse_cost, optional: true
-
1
option :saved_value, optional: true
-
1
option :metric_unit, optional: true
-
end
-
end
-
<%= react_component("ImpactCard",
-
{ title: title,
-
icon: icon,
-
iconRails: icon_rails,
-
balanceValue: balance_value,
-
reuseCost: reuse_cost,
-
savedValue: saved_value,
-
metricUnit: metric_unit
-
}, { class: "flex flex-1 min-w-auto" })
-
%>
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
option :inline, Types::Bool, default: -> { false }
-
1
option :id, Types::String, optional: true
-
1
option :name, Types::String, optional: true
-
1
option :submit_on_select, Types::Bool, default: -> { false }
-
-
1
option :range, Types::Bool, default: -> { false }
-
1
option :presets, Types::Bool, default: -> { false }
-
1
option :format, Types::String, default: -> { "date" }
-
1
option :locale, Types::String, default: -> { I18n.locale.to_s }
-
-
1
option :initial_date, Types::Date, optional: true
-
1
option :end_date, Types::Date, optional: true
-
-
1
def dom_id
-
id || "datepicker-#{SecureRandom.hex(4)}"
-
end
-
-
1
def variant
-
then: 0
else: 0
return :presets if presets
-
then: 0
else: 0
return :range if range
-
-
when: 0
case format.to_s
-
when: 0
when "month" then :month
-
else: 0
when "year" then :year
-
else :single_date
-
end
-
end
-
-
1
def field_wrapper_class
-
"lui-date_picker__field"
-
end
-
-
1
def input_name
-
then: 0
else: 0
return name || "date_range" if variant.in?([:range, :presets])
-
then: 0
else: 0
return name || "month" if variant == :month
-
then: 0
else: 0
return name || "year" if variant == :year
-
-
name || "date"
-
end
-
-
1
def input_class
-
then: 0
else: 0
variant == :range ? "lui-date_picker-ranged-picker" : "air-datepicker-input"
-
end
-
-
1
def placeholder
-
key =
-
when: 0
case variant
-
when: 0
when :month then "loopos_ui.date_picker.placeholder.select_month"
-
when: 0
when :year then "loopos_ui.date_picker.placeholder.select_year"
-
else: 0
when :range, :presets then "loopos_ui.date_picker.placeholder.select_date_range"
-
else "loopos_ui.date_picker.placeholder.select_date"
-
end
-
-
default =
-
when: 0
case variant
-
when: 0
when :month then "Select month..."
-
when: 0
when :year then "Select year..."
-
else: 0
when :range, :presets then "Select date range..."
-
else "Select date..."
-
end
-
-
I18n.t(key, default: default, locale: locale)
-
end
-
-
1
def label(key, default:)
-
I18n.t("loopos_ui.date_picker.#{key}", default: default, locale: locale)
-
end
-
-
1
def base_date_picker_data
-
{
-
controller: "date-picker",
-
"date-picker-locale-value": locale,
-
"date-picker-submit-on-select-value": submit_on_select,
-
}
-
end
-
-
1
def field_date_picker_data
-
case variant
-
when: 0
when :month
-
base_date_picker_data.merge(
-
"date-picker-start-view-value": "months",
-
"date-picker-min-view-value": "months",
-
"date-picker-date-format-value": "MMMM yyyy",
-
"date-picker-show-this-month-button-value": true,
-
"date-picker-this-month-label-value": label("this_month", default: "This month"),
-
"date-picker-show-clear-button-value": true,
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
)
-
when: 0
when :year
-
base_date_picker_data.merge(
-
"date-picker-start-view-value": "years",
-
"date-picker-min-view-value": "years",
-
"date-picker-date-format-value": "yyyy",
-
"date-picker-show-this-year-button-value": true,
-
"date-picker-this-year-label-value": label("this_year", default: "This year"),
-
"date-picker-show-clear-button-value": true,
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
)
-
when: 0
when :range
-
base_date_picker_data.merge(
-
"date-picker-range-value": true,
-
"date-picker-max-date-value": end_date,
-
"date-picker-show-today-button-value": true,
-
"date-picker-today-label-value": label("today", default: "Today"),
-
"date-picker-show-clear-button-value": true,
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
)
-
else: 0
else
-
base_date_picker_data.merge(
-
"date-picker-initial-date-value": initial_date,
-
"date-picker-show-today-button-value": true,
-
"date-picker-today-label-value": label("today", default: "Today"),
-
"date-picker-show-clear-button-value": true,
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
)
-
end
-
end
-
-
1
def presets_wrapper_data
-
{
-
controller: "dropdown-popover date-picker",
-
"dropdown-popover-placement-value": "bottom-start",
-
"dropdown-popover-auto-position-value": true,
-
"date-picker-range-value": true,
-
"date-picker-locale-value": locale,
-
"date-picker-today-label-value": label("today", default: "Today"),
-
"date-picker-now-label-value": label("now", default: "Now"),
-
"date-picker-this-week-label-value": label("this_week", default: "This week"),
-
"date-picker-this-month-label-value": label("this_month", default: "This month"),
-
"date-picker-this-year-label-value": label("this_year", default: "This year"),
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
"date-picker-submit-on-select-value": submit_on_select,
-
}
-
end
-
-
1
def presets_inline_calendar_data
-
base_date_picker_data.merge(
-
"date-picker-inline-value": true,
-
"date-picker-range-value": true,
-
"date-picker-show-clear-button-value": true,
-
"date-picker-clear-label-value": label("clear", default: "Clear"),
-
)
-
end
-
end
-
end
-
-
<%# TODO: remove lui-input classes, used temporary to match old styles %>
-
<div class="lui-date_picker h-[32px]!">
-
<% case variant %>
-
when: 0
<% when :month %>
-
<%= render LooposUi::DatePicker::Month.new(
-
field_wrapper_class: field_wrapper_class,
-
field_date_picker_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
when: 0
<% when :year %>
-
<%= render LooposUi::DatePicker::Year.new(
-
field_wrapper_class: field_wrapper_class,
-
field_date_picker_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
when: 0
<% when :range %>
-
<%= render LooposUi::DatePicker::Range.new(
-
field_date_picker_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
when: 0
<% when :presets %>
-
<%= render LooposUi::DatePicker::Presets.new(
-
presets_wrapper_data: presets_wrapper_data,
-
input_name: input_name,
-
placeholder: placeholder,
-
locale: locale,
-
label_proc: method(:label),
-
presets_inline_calendar_data: presets_inline_calendar_data
-
) %>
-
else: 0
<% else %>
-
<%= render LooposUi::DatePicker::Single.new(
-
field_wrapper_class: field_wrapper_class,
-
field_date_picker_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Field < LoopComponent
-
1
option :wrapper_class, optional: true
-
1
option :wrapper_data, optional: true
-
1
option :input_name
-
1
option :input_class
-
1
option :input_data, default: -> { {} }
-
1
option :placeholder
-
-
1
def normalized_wrapper_data
-
(wrapper_data || {}).compact
-
end
-
-
1
def normalized_input_data
-
(input_data || {}).compact.merge("date-picker-target": "input")
-
end
-
end
-
end
-
end
-
-
<%= tag.div(class: wrapper_class, data: normalized_wrapper_data) do %>
-
<%= tag.input(
-
name: input_name,
-
class: input_class,
-
readonly: true,
-
data: normalized_input_data,
-
placeholder: placeholder
-
) %>
-
<div class="lui-date_picker__icon">
-
<%= render LooposUi::Icon.new(icon: :calendar, size: 16, color: "currentColor") %>
-
</div>
-
<%= content %>
-
<% end %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Month < LoopComponent
-
1
option :field_wrapper_class
-
1
option :field_date_picker_data
-
1
option :input_name
-
1
option :input_class
-
1
option :placeholder
-
end
-
end
-
end
-
-
<%= render LooposUi::DatePicker::Field.new(
-
wrapper_class: field_wrapper_class,
-
wrapper_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Presets < LoopComponent
-
1
option :presets_wrapper_data
-
1
option :input_name
-
1
option :placeholder
-
1
option :locale, Types::String
-
1
option :label_proc
-
1
option :presets_inline_calendar_data
-
-
1
def label(key, default:)
-
label_proc.call(key, default: default)
-
end
-
end
-
end
-
end
-
-
<%= tag.div(class: "lui-date_picker__presets", data: presets_wrapper_data) do %>
-
<%= render LooposUi::DatePicker::Field.new(
-
wrapper_class: nil,
-
wrapper_data: {},
-
input_name: input_name,
-
input_class: "air-datepicker-input",
-
input_data: {
-
"dropdown-popover-target": "button",
-
action: "click->dropdown-popover#toggle"
-
},
-
placeholder: placeholder
-
) do %>
-
<dialog data-dropdown-popover-target="menu"
-
data-controller="menu"
-
data-action="click@document->dropdown-popover#closeOnClickOutside keydown.up->menu#prev keydown.down->menu#next keydown.up->menu#preventScroll keydown.down->menu#preventScroll"
-
class="outline-hidden absolute z-50 rounded-lg border border-neutral-200 bg-white text-neutral-800 shadow-md transition-opacity ease-out duration-150 opacity-0 [[open]]:scale-100 [[open]]:opacity-100"
-
data-menu-index-value="-1">
-
<div class="flex flex-col sm:flex-row">
-
<%= render LooposUi::DatePicker::PresetsMenu.new(locale: locale, label_proc: label_proc) %>
-
<%= render LooposUi::DatePicker::PresetsInlineCalendar.new(presets_inline_calendar_data: presets_inline_calendar_data) %>
-
</div>
-
</dialog>
-
<% end %>
-
<% end %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class PresetsInlineCalendar < LoopComponent
-
1
option :presets_inline_calendar_data
-
end
-
end
-
end
-
-
<!-- Right side: Inline calendar -->
-
<%= tag.div(class: "inline-calendar", data: presets_inline_calendar_data) do %>
-
<%= tag.input(class: "hidden", data: { "date-picker-target": "input" }) %>
-
<% end %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class PresetsMenu < LoopComponent
-
1
option :locale, Types::String
-
1
option :label_proc
-
-
1
def label(key, default:)
-
label_proc.call(key, default: default)
-
end
-
end
-
end
-
end
-
-
<!-- Left side: Time range presets -->
-
<div class="air-datepicker-presets-left-container">
-
<div class="air-datepicker-presets-left-container-buttons">
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setToday">
-
<%= label("today", default: "Today") %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setYesterday">
-
<%= label("yesterday", default: "Yesterday") %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setLastDays"
-
data-days="7">
-
<%= I18n.t("loopos_ui.date_picker.last_n_days", count: 7, default: "Last 7 days", locale: locale) %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setLastDays"
-
data-days="30">
-
<%= I18n.t("loopos_ui.date_picker.last_n_days", count: 30, default: "Last 30 days", locale: locale) %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setLastDays"
-
data-days="90">
-
<%= I18n.t("loopos_ui.date_picker.last_n_days", count: 90, default: "Last 90 days", locale: locale) %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setLastDays"
-
data-days="180">
-
<%= I18n.t("loopos_ui.date_picker.last_n_days", count: 180, default: "Last 180 days", locale: locale) %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setLastDays"
-
data-days="365">
-
<%= I18n.t("loopos_ui.date_picker.last_n_days", count: 365, default: "Last 365 days", locale: locale) %>
-
</button>
-
</div>
-
-
<div class="air-datepicker-presets-left-divider-line"></div>
-
-
<div class="air-datepicker-presets-left-container-buttons">
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setPreset"
-
data-preset-type="this-month">
-
<%= label("this_month", default: "This month") %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setPreset"
-
data-preset-type="last-month">
-
<%= label("last_month", default: "Last month") %>
-
</button>
-
<button type="button"
-
class="air-datepicker-presets--button"
-
data-menu-target="item"
-
data-action="click->date-picker#setPreset"
-
data-preset-type="this-year">
-
<%= label("this_year", default: "This year") %>
-
</button>
-
</div>
-
-
<div class="air-datepicker-presets-left-divider-line"></div>
-
-
<div class="air-datepicker-presets-left-container-buttons">
-
<button type="button"
-
class="air-datepicker-presets--button-close"
-
data-menu-target="item"
-
data-action="click->date-picker#clearSelection">
-
<%= label("clear_and_close", default: "Clear & Close") %>
-
</button>
-
</div>
-
</div>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Range < LoopComponent
-
1
option :field_date_picker_data
-
1
option :input_name
-
1
option :input_class
-
1
option :placeholder
-
end
-
end
-
end
-
-
<%= render LooposUi::DatePicker::Field.new(
-
wrapper_class: nil,
-
wrapper_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Single < LoopComponent
-
1
option :field_wrapper_class
-
1
option :field_date_picker_data
-
1
option :input_name
-
1
option :input_class
-
1
option :placeholder
-
end
-
end
-
end
-
-
<%= render LooposUi::DatePicker::Field.new(
-
wrapper_class: field_wrapper_class,
-
wrapper_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
-
1
module LooposUi
-
1
class DatePicker < LoopComponent
-
1
class Year < LoopComponent
-
1
option :field_wrapper_class
-
1
option :field_date_picker_data
-
1
option :input_name
-
1
option :input_class
-
1
option :placeholder
-
end
-
end
-
end
-
-
<%= render LooposUi::DatePicker::Field.new(
-
wrapper_class: field_wrapper_class,
-
wrapper_data: field_date_picker_data,
-
input_name: input_name,
-
input_class: input_class,
-
placeholder: placeholder
-
) %>
-
-
1
module LooposUi
-
1
class DateShow < LoopComponent
-
1
option :date, type: Types::TDate, optinal: true, default: -> { nil }
-
1
option :format, default: -> { :long }, type: Types::Coercible::Symbol.enum(:short, :long)
-
-
1
def formatted_date
-
then: 0
else: 0
return "-" if date.blank?
-
-
case format
-
when: 0
when :long
-
date.strftime("%d-%m-%Y %Hh%M")
-
when: 0
when :short
-
date.strftime("%d-%m-%Y")
-
else
-
skipped
# :nocov:
-
skipped
end
-
skipped
end
-
skipped
end
-
skipped
end
-
<span class="lui-date-show"><%= formatted_date %></span>
-
1
module LooposUi
-
1
class Dots < LoopComponent
-
1
option :amount, default: -> { 1 }, type: Types::Integer
-
1
option :active, default: -> { 1 }, type: Types::Integer
-
3
option :data, default: -> { {} }, type: Types::Hash
-
3
option :dot_data, default: -> { {} }, type: Types::Hash
-
-
1
private
-
-
1
def data_attributes
-
2
deep_merge_args({ controller: "dots", dots_index_value: active - 1 }, data)
-
end
-
-
1
def dot_data_attributes
-
16
deep_merge_args({ dots_target: "dot", action: "click->dots#selectDot" }, dot_data)
-
end
-
end
-
end
-
4
<%= tag.div class: "lui-dots", data: data_attributes do %>
-
2
<% amount.times do |i| %>
-
16
then: 2
else: 14
<%= tag.div class: "lui-dot #{i + 1 == active ? "lui-dot--active" : ""}", data: dot_data_attributes %>
-
<% end %>
-
<% end %>
-
120
<div class="lui-double-state-label">
-
120
<%= render LooposUi::StateLabel.new(
-
text: leading_text,
-
color: leading_color || :default,
-
icon: leading_icon,
-
condensed: leading_condensed,
-
120
light: light) %>
-
120
<%= render LooposUi::StateLabel.new(
-
text: trailing_text,
-
color: trailing_color || :white,
-
icon: trailing_icon,
-
condensed: trailing_condensed,
-
120
light: light) %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class DoubleStateLabel < LoopComponent
-
1
option :leading_text, Types::Coercible::String
-
1
option :leading_color, Types::Coercible::String
-
1
option :leading_icon, Types::Coercible::String, optional: true
-
5
option :leading_condensed, Types::Bool, default: -> { false }
-
1
option :trailing_text, Types::Coercible::String
-
1
option :trailing_color, Types::Coercible::String
-
1
option :trailing_icon, Types::Coercible::String, optional: true
-
5
option :trailing_condensed, Types::Bool, default: -> { false }
-
1
option :icon, Types::Coercible::String, optional: true
-
1
option :light, Types::Bool, default: -> { false }
-
-
1
def initialize(...)
-
120
super
-
-
120
@trailing_icon ||= icon
-
end
-
end
-
end
-
1
module LooposUi
-
1
class DrawerBar < LoopComponent
-
1
renders_many :headers
-
1
renders_one :footer
-
-
1
option :close_on_outside_click, default: -> { true }
-
1
option :default_open, default: -> { false }
-
-
1
private
-
-
1
def state
-
then: 0
else: 0
default_open ? "open" : "closed"
-
end
-
-
1
def classes
-
"lui-drawer_bar lui-drawer_bar--#{state}"
-
end
-
-
1
class Header < LoopComponent
-
1
erb_template <<~HTML
-
<div class="flex justify-between">
-
<div class="flex flex-col p-4 gap-3 self-stretch">
-
<%= content %>
-
</div>
-
<div class="p-3">
-
<%= corner_actions %>
-
</div>
-
</div>
-
HTML
-
-
1
renders_one :corner_actions
-
end
-
-
1
class IconDisplay < LoopComponent
-
1
option :icon, optional: true
-
1
option :image, optional: true
-
1
option :svg, optional: true
-
-
1
erb_template <<~HTML
-
<div class="flex w-[40px] h-[40px] justify-center items-center rounded-md border border-solid border-general-gray-300 bg-general-gray-100 overflow-hidden">
-
then: 0
<% if icon.present? %>
-
else: 0
<%= tag.icon class: icon + " flex" %>
-
then: 0
<% elsif image.present? %>
-
<%= image_tag image, class: "w-full" %>
-
else: 0
<% else %>
-
<%= svg %>
-
<% end %>
-
</div>
-
HTML
-
end
-
-
1
class Entity < LoopComponent
-
1
erb_template <<~HTML
-
<div class="flex gap-2 flex-start">
-
<%= render LooposUi::DrawerBar::IconDisplay.new(icon: icon, svg: svg, image: image) %>
-
<%= render LooposUi::TitleDescription.new( title: title, description: description, size: "small") %>
-
</div>
-
HTML
-
-
1
option :icon, optional: true
-
1
option :image, optional: true
-
1
option :svg, optional: true
-
1
option :title
-
1
option :description
-
-
# def initialize(...)
-
# super
-
# if icon.nil? && image.nil?
-
# raise ArgumentError, "Either icon or image must be provided"
-
# end
-
# end
-
end
-
-
1
class ContentSection < LoopComponent
-
1
erb_template <<~HTML
-
<div class="flex justify-between border-b border-t border-solid border-gray-300 py-[16px] px-[24px]">
-
<%= render LooposUi::TitleDescription.new( title: title, description: description, size: "small") %>
-
<%= content %>
-
</div>
-
HTML
-
-
1
option :title
-
1
option :description
-
end
-
-
1
class Footer < LoopComponent
-
end
-
end
-
end
-
<%# TODO: prop for open, for preview %>
-
<div
-
class="<%= classes %>"
-
data-drawer-bar-target="component"
-
>
-
<div class="lui-drawer_bar__close_button" data-drawer-bar-target="closeButton" >
-
<%= render LooposUi::Button.new(
-
icon: "close",
-
type: :secondary,
-
kind: :neutral,
-
size: :tiny,
-
tag_options: {
-
data: { action: "click->drawer-bar#handleCloseButtonClick" }
-
}
-
)%>
-
</div>
-
<div class="lui-drawer_bar__drawer" data-drawer-bar-target="drawer" data-close-on-outside-click="<%= close_on_outside_click %>">
-
<div class="lui-drawer_bar__drawer__top">
-
<% headers.each do |header| %>
-
<%= header %>
-
<% end %>
-
</div>
-
<div class="lui-drawer_bar__drawer__body">
-
<%= content %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class DummySlot < LoopComponent
-
1
option :text, default: -> { "SLOT" }, type: Types::Coercible::String
-
1
option :classes, optional: true, type: Types::String
-
-
1
private
-
-
1
def all_classes
-
[
-
34
default_width_class,
-
default_height_class,
-
classes,
-
"lui-dummy_slot",
-
"text-[#78818a]", # should not be hardcoded, but this is dummy slot
-
"copy-12 font-bold",
-
"leading-none",
-
].compact.join(" ")
-
end
-
-
1
def default_width_class
-
44
then: 10
else: 24
then: 10
else: 24
else: 10
then: 24
"w-full" unless classes&.split&.find { |c| c.starts_with?("w-") }
-
end
-
-
1
def default_height_class
-
54
then: 10
else: 24
then: 10
else: 24
else: 10
then: 24
"w-full" unless classes&.split&.find { |c| c.starts_with?("h-") }
-
end
-
end
-
-
1
Dummy = DummySlot # Alias, I don't like "Slot" in the name
-
end
-
68
<%= tag.div(class: all_classes) do %>
-
34
<%= content || text %>
-
<% end %>
-
1
module LooposUi
-
1
class EmailPreview < LoopComponent
-
1
option :email_message, default: -> { "" }
-
end
-
end
-
<iframe id="emailIframe" width="100%" scrolling="no" style="border: none;" onload="setHeight()" srcdoc="<%= email_message || "" %>" ></iframe>
-
<script>
-
function setHeight()
-
{
-
var child = document.getElementById('emailIframe');
-
child.style.height = child.contentWindow.document.body.scrollHeight + "px";
-
}
-
</script>
-
1
module LooposUi
-
1
module Entities
-
1
class AppInstance < LooposUi::Entity
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
class << self
-
1
def from_hash(hash)
-
new(app_instance: OpenStruct.new(hash))
-
end
-
end
-
-
# FIXME: app instance needs more data to create new
-
-
1
def initialize(app_instance:, **kwargs)
-
super(**kwargs)
-
@text = app_instance.name
-
# When it's a new app instance, it doesn't have an url nor kind
-
@url = app_instance.try(:url) || nil
-
@href_options = app_instance.try(:href_options) || {}
-
@icon = app_instance.try(:kind) || app_instance.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Brand < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(brand:, **kwargs)
-
super(**kwargs)
-
@brand = brand
-
@text = @brand.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class CatalogNode < LooposUi::Entity
-
1
attr_reader :category
-
1
attr_reader :product
-
-
1
def initialize(category:, product:, **kwargs)
-
super(**kwargs)
-
@category = category
-
@product = product
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: @category, url: @url) %>
-
<% end %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Product.new(product: @product, url: @url) %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Entities
-
1
class CatalogSectionInheritedProtocol < LooposUi::Entity
-
1
with_icon :box
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(catalog_section_inherited_protocol:, **kwargs)
-
super(**kwargs)
-
@catalog_section_inherited_protocol = catalog_section_inherited_protocol
-
@text = @catalog_section_inherited_protocol.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class CatalogSectionProtocol < LooposUi::Entity
-
1
with_icon :box
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(catalog_section_protocol:, **kwargs)
-
super(**kwargs)
-
@catalog_section_protocol = catalog_section_protocol
-
@text = @catalog_section_protocol.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Category < LooposUi::Entity
-
1
with_icon :grid
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(category:, **kwargs)
-
super(**kwargs)
-
@category = category
-
@text = @category.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Email < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(email:, **kwargs)
-
super(**kwargs)
-
@email = email
-
@text = @email.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Flow < LooposUi::Entity
-
1
with_icon :timeline_arrow
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(flow:, **kwargs)
-
super(**kwargs)
-
@flow = flow
-
@text = @flow.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Identifier < LooposUi::Entity
-
1
with_color :general
-
1
initialize_with text: :text, key_text: :key_text
-
-
1
renders_many :actions
-
-
1
def initialize(identifier:, **kwargs)
-
super(**kwargs)
-
@identifier = identifier
-
@text = @identifier.text
-
@key_text = @identifier.key_text
-
@key_value_actions = kwargs.delete(:key_value_actions) || {
-
data: {
-
turbo_action: :advance,
-
},
-
}
-
@kind = :key_value
-
end
-
-
# Sobrescreve o template para passar as ações diretamente para o EntityToken
-
1
erb_template <<~ERB
-
<% token = entity %>
-
then: 0
else: 0
<% if actions.any? %>
-
<% actions.each do |action| %>
-
<% token.with_action do %>
-
<%= action %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render token %>
-
ERB
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class IncomingPayment < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(incoming_payment:, **kwargs)
-
super(**kwargs)
-
@incoming_payment = incoming_payment
-
@text = @incoming_payment.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class InheritedProtocol < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(inherited_protocol:, **kwargs)
-
super(**kwargs)
-
@inherited_protocol = inherited_protocol
-
@text = @inherited_protocol.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Invoice < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(invoice:, **kwargs)
-
super(**kwargs)
-
@invoice = invoice
-
@text = @invoice.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Item < LooposUi::Entity
-
1
with_icon :box
-
1
with_color :general
-
1
initialize_with full_id: :text
-
-
1
def initialize(item:, **kwargs)
-
super(**kwargs)
-
@item = item
-
@text = @item.full_id
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Partnable < LooposUi::Entity
-
1
with_icon :bars_staggered_tag
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(partnable:, **kwargs)
-
super(**kwargs)
-
@partnable = partnable
-
@text = @partnable.name
-
-
else: 0
@icon = case @partnable.icon
-
when ActiveStorage::Attached::One
-
when: 0
# Partner or PartnerGroup
-
then: 0
else: 0
@partnable.icon&.url
-
when String
-
when: 0
# PartnableResource::Partnable
-
@partnable.icon
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class PartnerGroup < LooposUi::Entity
-
1
with_icon :box
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(partner_group:, **kwargs)
-
super(**kwargs)
-
@partner_group = partner_group
-
@text = @partner_group.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Payment < LooposUi::Entity
-
1
with_icon :fa_seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(payment:, **kwargs)
-
super(**kwargs)
-
@payment = payment
-
@text = @payment.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class PricingRule < LooposUi::Entity
-
1
with_icon :euro_sign
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(pricing_rule:, **kwargs)
-
super(**kwargs)
-
@pricing_rule = pricing_rule
-
@text = @pricing_rule.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class PricingRuleInheritance < LooposUi::Entity
-
1
with_icon :euro_sign
-
1
with_trailing_icon :diagram_nested
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(pricing_rule:, **kwargs)
-
super(**kwargs)
-
@pricing_rule = pricing_rule
-
@text = @pricing_rule.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Product < LooposUi::Entity
-
1
with_icon :box_open
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(product:, **kwargs)
-
super(**kwargs)
-
@product = product
-
@text = @product.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Protocol < LooposUi::Entity
-
1
with_icon :bars_staggered
-
1
with_color :app
-
1
initialize_with name: :text
-
-
1
def initialize(protocol:, **kwargs)
-
super(**kwargs)
-
@protocol = protocol
-
@text = @protocol.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Provider < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(provider:, **kwargs)
-
super(**kwargs)
-
@provider = provider
-
@text = @provider.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Script < LooposUi::Entity
-
1
with_icon :code
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(script:, **kwargs)
-
super(**kwargs)
-
@script = script
-
@text = @script.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class ScriptKind < LooposUi::Entity
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(script_kind:, **kwargs)
-
super(**kwargs)
-
@script_kind = script_kind
-
@text = @script_kind.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Shipping < LooposUi::Entity
-
1
with_icon :truck
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(shipping:, **kwargs)
-
super(**kwargs)
-
@shipping = shipping
-
@text = @shipping.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Sms < LooposUi::Entity
-
1
with_icon :seal
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(sms:, **kwargs)
-
super(**kwargs)
-
@sms = sms
-
@text = @sms.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class Template < LooposUi::Entity
-
1
with_icon :file_pen
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(template:, **kwargs)
-
super(**kwargs)
-
@template = template
-
@text = @template.name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Entities
-
1
class UserGroup < LooposUi::Entity
-
1
with_icon :users
-
1
with_color :general
-
1
initialize_with name: :text
-
-
1
def initialize(user_group:, **kwargs)
-
super(**kwargs)
-
@user_group = user_group
-
@text = @user_group.name
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Entity < LoopComponent
-
1
extend FaviconAware
-
-
25
then: 0
else: 0
class_attribute :icon
-
3
then: 0
else: 0
class_attribute :trailing_icon
-
28
then: 0
else: 0
class_attribute :color
-
28
then: 0
else: 0
class_attribute :struct_mapping
-
-
1
class << self
-
# This is a generic implementation of the without_model method
-
# It can only be used if you define a struct_mapping class attribute on your Entity
-
# An alternative to defining a struct_mapping is to override this method in your Entity class
-
# Example:
-
# UserGroupStruct = ::Struct.new(:name, keyword_init: true)
-
# def without_model(text:, **kwargs)
-
# new(user_group: UserGroupStruct.new(name: text), **kwargs)
-
# end
-
1
def without_model(text:, **kwargs)
-
then: 0
else: 0
if struct_mapping.nil? || struct_mapping.empty? || !struct_mapping.values.include?(:text)
-
raise NotImplementedError, "
-
You need to define struct_mapping in the #{self} class, or override #{self}.without_model
-
struct_mapping should be a hash, and it should have at least one key named :text
-
"
-
end
-
-
# Get the first argument of the initialize method. Usually it's the symbolized name of the model
-
# eg: :user_group, :protocol, etc
-
arg = instance_method(:initialize).parameters.first.second
-
-
# Create a struct to hold the date. It's just an object that has accessors for the keys
-
struct = "::Struct::#{name.demodulize}Struct".safe_constantize
-
else: 0
if struct.nil?
-
then: 0
-
::Struct.new("#{name.demodulize}Struct", *struct_mapping.keys, keyword_init: true)
-
struct = "::Struct::#{name.demodulize}Struct".constantize
-
end
-
-
# Instanciate the Entity with the struct, using the passed args
-
args = struct_mapping.map { |k, v| [k, binding.local_variable_get(v)] }.to_h
-
new(arg => struct.new(**args), **kwargs)
-
end
-
-
1
private
-
-
1
def with_icon(icon)
-
23
self.icon = faviconize(icon)
-
end
-
-
1
def with_trailing_icon(icon)
-
1
self.trailing_icon = faviconize(icon)
-
end
-
-
1
def with_color(color)
-
# TODO: check valid values
-
26
self.color = color
-
end
-
-
1
def initialize_with(struct_mapping)
-
26
self.struct_mapping = struct_mapping
-
end
-
end
-
-
1
attr_reader :url
-
1
attr_reader :href_options
-
1
attr_accessor :text
-
-
1
def initialize(url: nil, href_options: {}, **kwargs)
-
@url = url
-
@href_options = href_options
-
@tooltip = kwargs.delete(:tooltip)
-
@kwargs = kwargs
-
end
-
-
1
def entity
-
@entity ||= LooposUi::EntityToken.new(
-
color: self.class.color,
-
icon: @icon || self.class.icon,
-
trailing_icon: self.class.trailing_icon,
-
text: text,
-
url: url,
-
tooltip: @tooltip,
-
href_options: href_options,
-
kind: @kind,
-
key_text: @key_text,
-
key_value_actions: @key_value_actions,
-
**@kwargs,
-
)
-
end
-
-
1
then: 0
else: 0
delegate :with_action, to: :entity
-
-
1
erb_template <<~ERB
-
<%= render entity %>
-
ERB
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class EntityToken < LooposUi::Token
-
COLORS = {
-
# text, bg
-
1
general: [find_color("general-global-black"), nil],
-
}
-
-
APP_COLORS = {
-
# text, bg
-
1
manager: [find_color("apps-800-primary", LooposUi.config.app_type), find_color("apps-300", LooposUi.config.app_type)], # Apps/Manager/800-Primary, Apps/Manager/300
-
core: [find_color("apps-800-primary", LooposUi.config.app_type), find_color("apps-300", LooposUi.config.app_type)], # Apps/Core/800-Primary, Apps/Core/300
-
hubs: [find_color("apps-800-primary", LooposUi.config.app_type), find_color("apps-300", LooposUi.config.app_type)], # Apps/Hubs/800-Primary, Apps/Hubs/300
-
}
-
-
1
def initialize(**kwargs)
-
24
@draft = kwargs.delete(:draft)
-
24
@url = kwargs.delete(:url)
-
24
@href_options = kwargs.delete(:href_options) || {}
-
24
@key_text = kwargs.delete(:key_text)
-
24
@key_value_actions = kwargs.delete(:key_value_actions)
-
24
@kind = kwargs.delete(:kind)
-
24
super(kind: @kind, key_text: @key_text, key_value_actions: @key_value_actions, **kwargs)
-
-
24
@color ||= :general
-
24
set_type
-
end
-
-
1
def classes
-
[
-
22
"lui-entity-token",
-
"lui-entity-token-#{@type}",
-
22
then: 0
else: 22
draft? ? "lui-entity-token--draft" : "",
-
22
then: 0
else: 22
@disabled ? "lui-token--disabled" : "",
-
].compact.join(" ")
-
end
-
-
1
def styles
-
22
then: 0
else: 22
cursor_style = @draggable ? "cursor: grab;" : ""
-
22
then: 0
else: 22
bg_style = @bg_color.present? ? "background-color: #{@bg_color};" : ""
-
-
22
<<~CSS.squish
-
color: #{@text_color};
-
#{bg_style}
-
#{cursor_style}
-
CSS
-
end
-
-
1
private
-
-
1
def set_color(color)
-
# TODO: Restrecture with LoopOS UI Colros component
-
24
then: 0
@text_color, @bg_color = if color.present?
-
then: 0
if COLORS.key?(color.to_sym)
-
else: 0
COLORS[color.to_sym]
-
then: 0
elsif color.to_sym == :app
-
APP_COLORS[LooposUi.config.app_type.to_sym] || COLORS[:general]
-
else: 0
else
-
raise ArgumentError, "Invalid color: #{color}, available colors: #{COLORS.keys.join(", ")}, app."
-
end
-
else: 24
else
-
24
COLORS.excluding(LooposUi.config.app_type.to_sym).values.sample
-
end
-
end
-
-
1
def set_type
-
24
then: 24
else: 0
@type = case @color&.to_sym
-
when: 0
when :app
-
then: 0
else: 0
APP_COLORS.key?(LooposUi.config.app_type.to_sym) ? LooposUi.config.app_type : :general
-
else: 24
else
-
24
@color
-
end
-
end
-
-
1
def draft?
-
22
@draft
-
end
-
end
-
end
-
1
module LooposUi
-
1
class ErrorPage < LoopComponent
-
1
option :error, type: Types::Integer
-
1
option :app, default: proc { LooposUi.config.app_type }, type: Types::Coercible::String
-
-
1
def error_text
-
{
-
403 =>
-
{
-
title: "The access to this page is denied",
-
description: "You do not have access to this page or resource.
-
Please check the URL for any typos or visit our <a class=\"lui-error-page__description__link\" href=\"/\">Home</a>.",
-
},
-
404 =>
-
{
-
title: "This page cannot be found",
-
description: "The URL is incorrect or the page has been moved. Please check the URL for any typos.<br>
-
Visit our <a class=\"lui-error-page__description__link\" href=\"/\">Home</a> or select another page from the menu.",
-
},
-
500 =>
-
{
-
title: "Something went wrong on our end",
-
description: "An issue is blocking your request. Please refresh the page.<br>
-
If it persists, try again later or <a class=\"lui-error-page__description__link\" href=\"mailto:support@loop-os.com\">contact us</a>.",
-
},
-
}
-
end
-
-
end
-
end
-
-
<div class="lui-error-page">
-
<%= helpers.app_svg(app: app, width: 120) %>
-
<div class="lui-error-page__content">
-
<div class="lui-error-page__header">
-
<p class="lui-error-page__header__error">
-
Error <%= error %>
-
</p>
-
<p class="lui-error-page__header__title">
-
<%= error_text.dig(error, :title) %>
-
</p>
-
</div>
-
<p class="lui-error-page__description">
-
<%= sanitize error_text.dig(error, :description) %>
-
</p>
-
then: 0
else: 0
<% if error == 500 %>
-
<div class="pt-4">
-
<%= render LooposUi::Button.new(
-
text: "Refresh",
-
size: :default,
-
type: :secondary,
-
tag_options: {
-
"onClick" => "location.href=location.href"
-
}) %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ExtraDataViewer < LoopComponent
-
1
VIEW_MODES = ["human", "json"].freeze
-
-
1
option :title, type: Types::String.optional, optional: true
-
1
option :data, type: Types::Hash | Types::Array | Types::String
-
1
option :readonly, type: Types::Bool, default: -> { false }
-
1
option :default_view, type: Types::String, default: -> { "human" }
-
-
1
option :inline_element, optional: true
-
1
option :inline_attribute, type: Types::String.optional, optional: true
-
1
option :inline_show_path, type: Types::String.optional, optional: true
-
1
option :inline_edit_path, type: Types::String.optional, optional: true
-
1
option :inline_form_url, type: Types::String.optional, optional: true
-
1
option :inline_input_name, type: Types::String.optional, optional: true
-
1
option :inline_show_edit_button, type: Types::Bool, default: -> { true }
-
1
option :inline_turbo_id, type: Types::String.optional, optional: true
-
1
option :inline_hidden_fields, type: Types::Hash, default: -> { {} }
-
-
1
def current_view
-
then: 0
else: 0
VIEW_MODES.include?(default_view) ? default_view : "human"
-
end
-
-
1
def editor_readonly?
-
readonly
-
end
-
-
1
def json_code
-
when: 0
case data
-
when: 0
when String then data
-
when Hash, Array then data.to_json
-
else: 0
else
-
data.to_s
-
end
-
end
-
end
-
end
-
<%
-
then: 0
else: 0
turbo_wrapper_id = inline_turbo_id if inline_element.present? && inline_turbo_id.present?
-
persisted_view = params[:extra_data_viewer_default_view].presence
-
then: 0
else: 0
selected_view = LooposUi::ExtraDataViewer::VIEW_MODES.include?(persisted_view) ? persisted_view : current_view
-
editor_hidden_fields = inline_hidden_fields.merge("extra_data_viewer_default_view" => selected_view)
-
%>
-
<% inner_content = capture do %>
-
<%= tag.div(
-
id: random_id,
-
class: "lui-extra_data_viewer",
-
data: {
-
controller: "extra-data-viewer",
-
action: "change->extra-data-viewer#toggleView",
-
extra_data_viewer_default_view_value: selected_view,
-
extra_data_viewer_persist_field_value: "extra_data_viewer_default_view",
-
extra_data_viewer_storage_key_value: turbo_wrapper_id
-
}
-
) do %>
-
<div class="lui-extra_data_viewer__toggle">
-
<%= render LooposUi::Toggle.new(
-
label: t("loopos_ui.extra_data_viewer.json_view"),
-
value: selected_view == "json",
-
attrs: {
-
data: {
-
view_toggle: "json",
-
"extra-data-viewer-target": "jsonToggle"
-
}
-
}
-
) %>
-
</div>
-
-
<%= tag.div(
-
data: { extra_data_viewer_target: "humanPanel" },
-
then: 0
else: 0
class: "lui-extra_data_viewer__panel lui-extra_data_viewer__panel--human #{selected_view == "human" ? "" : "lui-extra_data_viewer__panel--hidden"}"
-
) do %>
-
<%= render LooposUi::HumanView.new(
-
title: title,
-
data: data
-
) %>
-
<% end %>
-
-
<%= tag.div(
-
data: { extra_data_viewer_target: "jsonPanel" },
-
then: 0
else: 0
class: "lui-extra_data_viewer__panel lui-extra_data_viewer__panel--json #{selected_view == "json" ? "" : "lui-extra_data_viewer__panel--hidden"}"
-
) do %>
-
<%= render LooposUi::CodeEditor.new(
-
type: "json",
-
code: json_code,
-
readonly: editor_readonly?,
-
element: inline_element,
-
attribute: inline_attribute,
-
show_path: inline_show_path,
-
input_name: inline_input_name,
-
show_edit_button: inline_show_edit_button,
-
turbo_id: inline_turbo_id,
-
with_turbo_wrapper: false,
-
hidden_fields: editor_hidden_fields,
-
title: title
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<%= turbo_wrapper_id.present? ? helpers.turbo_frame_tag(turbo_wrapper_id) { inner_content } : inner_content %>
-
1
module LooposUi
-
1
module Features
-
1
class List < LoopComponent
-
1
ORDER = [:flows, :protocols, :pricing_rules, :forward_trade_in, :acceptable, :acceptable_online, :saleable_as_new]
-
-
ICONS = {
-
1
flows: "fa-regular fa-timeline-arrow",
-
protocols: "fa-regular fa-bars-staggered",
-
pricing_rules: "fa-regular fa-euro-sign",
-
forward_trade_in: "",
-
acceptable: "",
-
acceptable_online: "",
-
saleable_as_new: "",
-
}
-
-
1
option :list
-
end
-
end
-
end
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% ORDER.each do |feature| %>
-
<% details = list[feature] %>
-
then: 0
else: 0
<% if details.present? %>
-
<% token_list.with_token_manual do %>
-
<%= render(LooposUi::EntityToken.new(
-
text: details[:label] || feature.to_s.titleize,
-
icon: ICONS[feature],
-
url: details[:url],
-
tooltip: details[:tooltip],
-
color: :general
-
)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<button id="dropdownBgHoverButton" data-dropdown-toggle="dropdownBgHover" class="loopui-filters-toggle border-opacity-10 bg-background border-primary-dark" type="button">
-
<i class="fa-sharp fa-regular fa-filter"></i>
-
<div class="loopui-filters-badge hidden">20</div>
-
</button>
-
-
then: 0
else: 0
<% if @has_active_filters %>
-
<div class='loopui-filters-clear'>
-
<%= link_to 'Clear all filters', URI.parse(@clear_path.call(**@clear_parameters.merge(turbo_frame: index_turbo_frame))).to_s,
-
data: {
-
turbo_action: "replace",
-
turbo_method: @method,
-
} %>
-
</div>
-
<% end %>
-
-
<div id="dropdownBgHover" data-controller='filters-dropdown' class="hidden loopui-filters-dropdown">
-
<div class="loopui-filters-dropdown__back hidden" data-filters-dropdown-target='back' data-action='click->filters-dropdown#backButtonClick'>
-
<i class="fa-regular fa-arrow-left mr-2 text-sm"></i>
-
<span class="copy-14-medium text-black">Back</span>
-
</div>
-
-
<ul class="loopui-filters-dropdown__wrapper" data-filter-state='main-filter-wrapper' aria-labelledby="dropdownSearchButton">
-
<% @filters_titles.each do |data| %>
-
<li class="cursor-pointer">
-
<div class="loopui-filters-dropdown__options-wrapper group">
-
<label class="loopui-filters-dropdown__label" data-child='<%= data[:value] %>_children' data-action='click->filters-dropdown#onLabelClick'><%= data[:label] %></label>
-
<i class="fa-regular fa-chevron-right mr-2 group-hover:text-app-800-primary" data-child='<%= data[:value] %>_children' data-action='click->filters-dropdown#onLabelClick'></i>
-
</div>
-
</li>
-
<% end %>
-
</ul>
-
-
<% @filters_data.each do |micro_key, states| %>
-
<ul class="loopui-filters-dropdown__wrapper hidden" data-filter-state='<%= micro_key %>_children' aria-labelledby="dropdownSearchButton">
-
<% states.each do |data| %>
-
<li class="cursor-pointer">
-
<div class="loopui-filters-dropdown__options-wrapper group cursor-pointer" data-action="click->filters-dropdown#inputCheck">
-
then: 0
else: 0
<%= check_box_tag "q[#{data[:filter_param]}][]", data[:value], params.dig(:q, data[:filter_param])&.include?(data[:value].to_s), class: 'w-4 h-4 text-blue-600 bg-gray-100 border-gray-base rounded focus:ring-trasparent focus:ring-0 cursor-pointer', data: { 'filters-dropdown-target' => 'input', 'action' => 'change->filters-dropdown#checkboxChanged' } %>
-
<label class="loopui-filters-dropdown__label max-w-[120px]" data-action="click->filters-dropdown#inputCheck"><%= data[:label] %></label>
-
</div>
-
</li>
-
<% end %>
-
</ul>
-
<% end %>
-
<div class='flex'>
-
<input type='submit' value='Apply' data-filters-dropdown-target='submit' class="loopui-filters-dropdown__submit" />
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Filter
-
1
class FiltersDropdownComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
attr_reader :filters_data,
-
:filters_titles,
-
:index_turbo_frame,
-
:has_active_filters,
-
:clear_path,
-
:clear_parameters
-
-
1
def initialize(filters_data:, filters_titles:, index_turbo_frame:, has_active_filters: false, clear_path: "",
-
method: :get, clear_parameters: {})
-
@filters_data = filters_data
-
@index_turbo_frame = index_turbo_frame
-
@has_active_filters = has_active_filters
-
@clear_path = clear_path
-
@filters_titles = filters_titles
-
@clear_parameters = clear_parameters
-
@method = method
-
end
-
end
-
end
-
end
-
<div class='loopui-search-input'>
-
<div class="loopui-search-input__container">
-
<label for="default-search" class="loopui-search-input__label">Search</label>
-
<div class="loopui-search-input__wrapper">
-
<div class="loopui-search-input__icon">
-
<svg aria-hidden="true" class="loopui-search-input__icon-svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
-
</svg>
-
</div>
-
<%= search_field_tag @param, @search_query, class: 'loopui-search-input__field', placeholder: @placeholder %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Filter
-
1
class SearchComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
attr_reader :param, :search_query, :placeholder
-
-
1
def initialize(param:, search_query:, placeholder: "")
-
@param = param
-
@search_query = search_query
-
@placeholder = placeholder
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class FilterBar < LoopComponent
-
1
renders_many :filter_pills, LooposUi::FilterPill
-
-
1
option :buttons, Types::Array.of(Types::Hash), default: -> { [] }
-
-
1
option :show_filter_buttons, Types::Bool, default: -> { false }
-
1
option :show_toggle_switch, Types::Bool, default: -> { false }
-
-
1
option :search_options, Types::Hash, default: -> { {} }
-
-
1
option :toggle_options, Types::Hash, default: -> { {} }
-
-
1
option :mode, Types::Symbol, default: -> { :both }
-
-
1
option :table_id, Types::Coercible::String, optional: true
-
-
1
def search_options_for_render
-
search_options.compact
-
end
-
-
1
def filter_pills_exists?
-
filter_pills.present? && table_id.present?
-
end
-
-
1
def has_filters_pill?
-
pills_enabled? && table_id.present?
-
end
-
-
1
def has_filter_header?
-
header_enabled? && (search_options.present? || show_buttons? || show_toggle?)
-
end
-
-
1
def filter_key_for_pill(pill)
-
pill_extra = pill.extra_options || {}
-
pill_data = pill_extra[:data] || pill_extra["data"] || {}
-
filter_key = pill_data[:filter_key] || pill_data["filter_key"] || pill_data[:filterKey] || pill_data["filterKey"]
-
-
filter_key || pill_extra[:filter_key] || pill_extra["filter_key"]
-
end
-
-
1
def toggle_options_table
-
toggle_opts = toggle_options.dup
-
-
toggle_opts[:position] = :left
-
toggle_opts[:size] = :normal
-
-
else: 0
if table_id.present?
-
then: 0
-
existing_action = toggle_opts.dig(:data_action) || ""
-
toggle_opts[:data_action] = [existing_action, "change->lui--filter-toggle#handleToggleChange"].reject(&:blank?).join(" ")
-
end
-
-
toggle_opts
-
end
-
-
1
def button_options_for_render(button_options)
-
button_opts = button_options.dup
-
button_opts[:tag_options] ||= {}
-
button_opts[:tag_options][:data] ||= {}
-
existing_action = button_opts.dig(:tag_options, :data, :action) || ""
-
button_opts[:tag_options][:data][:action] = [existing_action, "click->lui--filter-buttons#handleButtonClick"].reject(&:blank?).join(" ")
-
-
button_opts[:tag_options][:type] ||= "button"
-
-
then: 0
else: 0
if button_opts.dig(:tag_options, :name).present?
-
button_opts[:tag_options][:data][:filter_name] = button_opts.dig(:tag_options, :name)
-
end
-
-
button_opts
-
end
-
-
1
private
-
-
1
def header_enabled?
-
[:both, :header].include?(mode)
-
end
-
-
1
def pills_enabled?
-
[:both, :pills].include?(mode)
-
end
-
-
1
def show_search?
-
show_search
-
end
-
-
1
def show_actions?
-
show_buttons? || show_toggle?
-
end
-
-
1
def show_buttons?
-
show_filter_buttons
-
end
-
-
1
def show_toggle?
-
show_toggle_switch
-
end
-
end
-
end
-
<%= tag.div(
-
class: classes,
-
data: {
-
controller: "lui--filter-buttons lui--filter-toggle lui--filter-pills",
-
"lui--filter-pills-table-id-value": table_id,
-
"lui--filter-buttons-table-id-value": table_id,
-
"lui--filter-toggle-table-id-value": table_id
-
}
-
) do %>
-
then: 0
else: 0
<% if has_filter_header? %>
-
<div class="lui-filter_bar__top">
-
then: 0
else: 0
<% if search_options.present? %>
-
<div class="lui-filter_bar__search">
-
<%= render LooposUi::Inputs::Search.new(**search_options_for_render) %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if show_actions? %>
-
<div class="lui-filter_bar__actions">
-
then: 0
else: 0
<% if show_buttons? %>
-
<div class="lui-filter_bar__button">
-
<% buttons.each_with_index do |button_options, index| %>
-
<%= render LooposUi::FilterButton.new(**button_options_for_render(button_options)) %>
-
<% end %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if show_toggle? %>
-
<div class="lui-filter_bar__toggle">
-
<%= render LooposUi::Toggle.new(**toggle_options_table) %>
-
</div>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if has_filters_pill? %>
-
<%= tag.div(
-
then: 0
else: 0
class: "lui-filter_bar__pills #{filter_pills_exists? ? "" : "lui-filter_bar__pills--empty"}",
-
data: {
-
controller: "lui--filter-pills",
-
"lui--filter-pills-table-id-value": table_id,
-
"lui--filter-pills-target": "div"
-
}
-
) do %>
-
<div class="lui-filter_bar__pills-list" data-lui--filter-pills-target="container">
-
<% filter_pills.each do |pill| %>
-
<%= tag.div(
-
data: {
-
"lui--filter-pills-target": "pill"
-
then: 0
else: 0
}.tap { |h| h["filter-key"] = filter_key_for_pill(pill) if filter_key_for_pill(pill).present? }
-
) do %>
-
<%= pill %>
-
<% end %>
-
<% end %>
-
</div>
-
<div class="lui-filter_bar__clear_all" data-lui--filter-pills-target="clearAllButton" style="display: none;">
-
<%= render LooposUi::Button.new(
-
text: t(".clear_all_filters"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :small,
-
tag_options: {
-
type: "button",
-
data: {
-
action: "click->lui--filter-pills#clearAll"
-
}
-
}
-
) %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
# rubocop:disable Naming/MethodName
-
1
class FilterButton < LoopComponent
-
1
include LooposUi::FaviconAware
-
-
1
SIZES = [
-
:large, :medium, :small,
-
].freeze
-
-
1
STATES = [
-
:enabled,
-
:hover,
-
:active,
-
:loading,
-
:focus,
-
:disabled,
-
].freeze
-
-
1
APP_ICONS = [
-
"core",
-
"manager",
-
"submission",
-
"hubs",
-
"validation",
-
"handling",
-
"exits",
-
"impact",
-
"submission_extra",
-
].freeze
-
-
1
renders_one :left_icon, ->(icon:, **kwargs) {
-
then: 0
if APP_ICONS.include?(icon.to_s)
-
else: 0
icon
-
then: 0
elsif MIcon.icon?(icon)
-
MIcon.new(icon, **kwargs)
-
else: 0
else
-
content_tag(:i, "", class: "lui-filter_button__icon lui-filter_button__icon--#{@size} #{icon}")
-
end
-
}
-
-
1
renders_one :right_icon, ->(icon:, **kwargs) {
-
then: 0
if APP_ICONS.include?(icon.to_s)
-
else: 0
icon
-
then: 0
elsif MIcon.icon?(icon)
-
MIcon.new(icon, **kwargs)
-
else: 0
else
-
content_tag(:i, "", class: "lui-filter_button__icon lui-filter_button__icon--#{@size} #{icon}")
-
end
-
}
-
-
1
option :text, Types::Coercible::String, optional: true
-
1
option :size, Types::Symbol.enum(*SIZES), optional: true
-
1
option :state, Types::Symbol.enum(*STATES), optional: true
-
# TODO: Rename this to snake_case
-
1
option :isSelected, Types::Bool, optional: true
-
1
option :showNotification, Types::Bool, optional: true
-
1
option :showRightIcon, Types::Bool, optional: true
-
1
option :showLeftIcon, Types::Bool, optional: true
-
1
option :showCounter, Types::Bool, optional: true
-
# Counter appearance (delegates to LooposUi::Counter)
-
# kind: :success, :danger, :warning, :neutral, :informative
-
1
option :counter_kind, Types::Symbol.optional, default: -> { :neutral }
-
1
option :left_icon, Types::Coercible::String, optional: true
-
1
option :right_icon, Types::Coercible::String, optional: true
-
1
option :count, Types::Coercible::Integer, optional: true
-
1
option :href, Types::Coercible::String, optional: true
-
1
option :tag, Types::Symbol, optional: true
-
1
option :tag_options, Types::Hash, default: -> { {} }
-
1
option :name, Types::Coercible::String, optional: true
-
-
1
mod :size
-
1
mod :state
-
1
mod :isSelected, condition: -> { isSelected? }
-
-
1
attr_reader :tag
-
-
# rubocop:disable Naming/VariableName
-
1
def initialize(
-
text: nil,
-
size: nil,
-
state: nil,
-
isSelected: nil,
-
showNotification: nil,
-
showRightIcon: nil,
-
showLeftIcon: nil,
-
showCounter: nil,
-
left_icon: nil,
-
right_icon: nil,
-
count: nil,
-
href: nil,
-
tag: nil,
-
tag_options: {},
-
name: nil,
-
**kwargs
-
)
-
@text = text
-
@size = size || :medium
-
@state = state || :enabled
-
@isSelected = isSelected || false
-
@showNotification = showNotification || false
-
@showRightIcon = showRightIcon || false
-
@showLeftIcon = showLeftIcon || false
-
@showCounter = showCounter || false
-
@left_icon = left_icon
-
@right_icon = right_icon
-
@count = count
-
@href = href
-
then: 0
else: 0
@tag = tag || (href.present? && @state != :disabled ? :a : :button)
-
@tag_options = tag_options
-
@name = name
-
-
else: 0
then: 0
raise ArgumentError, "Invalid size: #{@size}" unless SIZES.include?(@size)
-
else: 0
then: 0
raise ArgumentError, "Invalid state: #{@state}" unless STATES.include?(@state)
-
-
super(**kwargs)
-
end
-
# rubocop:enable Naming/VariableName
-
-
1
def before_render
-
then: 0
else: 0
with_left_icon(icon: @left_icon) if @left_icon.present? && showLeftIcon?
-
then: 0
else: 0
with_right_icon(icon: @right_icon) if @right_icon.present? && showRightIcon?
-
end
-
-
1
def classes
-
[
-
own_class,
-
mod(:size),
-
mod(:state),
-
mod(:isSelected),
-
additional_classes,
-
].compact.join(" ")
-
end
-
-
1
def tag_options
-
then: 0
options = if @tag == :a && @state != :disabled
-
{ href: @href }.merge!(@tag_options)
-
else: 0
else
-
@tag_options || {}
-
end
-
then: 0
else: 0
options.merge!(disabled: true) if @state == :disabled
-
-
# deep_merge_args preserves top-level keys from options (like name, type)
-
# but merges nested hashes (like data)
-
# Ensure type is set to "button" if not specified (prevents form submission)
-
base_options = {
-
class: classes,
-
data: {
-
controller: "lui--filter-button",
-
},
-
}
-
# Only set default type if tag is button and type is not already specified
-
then: 0
else: 0
if @tag == :button && !options.key?(:type)
-
base_options[:type] = "button"
-
end
-
-
then: 0
else: 0
options[:name] ||= @name if @name.present?
-
-
deep_merge_args(base_options, options)
-
end
-
-
1
def disabled?
-
@state == :disabled
-
end
-
-
1
def isSelected?
-
@isSelected
-
end
-
-
1
def showNotification?
-
@showNotification
-
end
-
-
1
def showRightIcon?
-
@showRightIcon
-
end
-
-
1
def showLeftIcon?
-
@showLeftIcon
-
end
-
-
1
def showCounter?
-
@showCounter && @count.present? && @count > 0
-
end
-
-
1
attr_reader :state
-
-
1
attr_reader :size
-
end
-
# rubocop:enable Naming/MethodName
-
end
-
<%= content_tag(tag, tag_options) do %>
-
then: 0
else: 0
<% if showNotification? %>
-
<span class="lui-filter_button__notification"></span>
-
<% end %>
-
then: 0
else: 0
<% if @state == :loading %>
-
<svg aria-hidden="true" class="lui-filter_button__spinner" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
-
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
-
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
-
</svg>
-
<% end %>
-
then: 0
else: 0
<% if showLeftIcon? && @left_icon.present? && @state != :loading %>
-
<%= render LooposUi::MIcon.new(@left_icon.to_sym, tag: :i) %>
-
<% end %>
-
then: 0
else: 0
<% if text.present? && @state != :loading %>
-
<span class="lui-filter_button__text">
-
<%= text %>
-
</span>
-
<% end %>
-
then: 0
else: 0
<% if showCounter? && @state != :loading && count.present? %>
-
<%= render LooposUi::Counter.new(
-
count: count,
-
kind: counter_kind || :neutral,
-
size: :tinny
-
) %>
-
<% end %>
-
then: 0
else: 0
<% if showRightIcon? && @right_icon.present? && @state != :loading %>
-
<%= render LooposUi::MIcon.new(@right_icon.to_sym, tag: :i) %>
-
<% end %>
-
then: 0
else: 0
<% if @state == :loading && text.present? %>
-
<span class="lui-filter_button__text lui-filter_button__text--loading">
-
<%= text %>
-
</span>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class FilterPill < LoopComponent
-
1
use_extra_options
-
-
1
SIZES = [
-
:small,
-
:medium,
-
].freeze
-
-
1
STATES = [
-
:enabled,
-
:active,
-
:state2,
-
].freeze
-
-
1
option :text, Types::Coercible::String
-
1
option :size, Types::Symbol.enum(*SIZES), default: -> { :medium }
-
1
option :state, Types::Symbol.enum(*STATES), default: -> { :enabled }
-
1
option :selected, Types::Bool, default: -> { false }
-
1
option :hasCloseButton, Types::Bool, default: -> { true }
-
1
option :count, Types::Coercible::Integer.optional, optional: true
-
-
1
mod :size
-
1
mod :state
-
1
mod :selected, condition: -> { selected? }
-
-
1
def selected?
-
selected
-
end
-
-
1
def show_count?
-
count.present? && count.positive?
-
end
-
-
1
def has_close_button?
-
:hasCloseButton
-
end
-
-
1
def classes
-
[
-
own_class,
-
mod(:size),
-
mod(:state),
-
mod(:selected),
-
additional_classes,
-
].compact.join(" ")
-
end
-
end
-
end
-
<%= tag.button type: "button", class: classes, **extra_options do %>
-
<span class="lui-filter_pill__label">
-
<%= text %>
-
</span>
-
then: 0
else: 0
<% if show_count? %>
-
<span class="lui-filter_pill__count">
-
<%= count %>
-
</span>
-
<% end %>
-
then: 0
else: 0
<% if has_close_button? %>
-
<%= render LooposUi::MIcon.new(
-
:close,
-
class: "lui-filter_pill__close",
-
data: { action: "click->lui--filter-pills#removePill" },
-
onclick: "event.preventDefault(); event.stopPropagation();"
-
) %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class FlexLayout < LoopComponent
-
1
renders_many :sections, ->(**kwargs) do
-
24
LooposUi::FlexLayout::Section.new(size: size, grow: grow, **kwargs)
-
end
-
-
3
option :size, default: -> { :full }, type: MediaQuerySizesConcern::Size
-
1
option :grow, Types::Bool, default: -> { true }
-
-
1
class Section < LoopComponent
-
1
include MediaQuerySizesConcern
-
-
1
option :size, MediaQuerySizesConcern::Size, optional: true
-
1
option :grow, Types::Bool, default: -> { true }
-
-
1
def classes
-
[
-
24
then: 24
else: 0
grow ? "grow" : nil,
-
size_class(size || :full),
-
media_query_classes,
-
].compact.uniq.join(" ")
-
end
-
end
-
end
-
end
-
2
<div class="lui-flex_layout">
-
2
<% sections.each do |section| %>
-
24
<%= section %>
-
<% end %>
-
2
</div>
-
1
module LooposUi
-
1
class FlexLayout
-
1
module MediaQuerySizesConcern
-
1
extend ActiveSupport::Concern
-
-
1
Size = Types::Symbol.enum(:full, :half) | Types::Integer.constrained(gteq: 1, lteq: 12)
-
-
1
included do
-
1
option :sm, optional: true, type: Size
-
1
option :md, optional: true, type: Size
-
1
option :lg, optional: true, type: Size
-
1
option :xl, optional: true, type: Size
-
1
option :xxl, optional: true, type: Size
-
-
1
def media_query_classes
-
[
-
24
size_class(sm, :sm),
-
size_class(md, :md),
-
size_class(lg, :lg),
-
size_class(xl, :xl),
-
size_class(xxl, :"2xl"),
-
].compact.join(" ")
-
end
-
end
-
-
1
private
-
-
1
def size_class(size, query = nil)
-
144
then: 0
else: 144
return if size.nil?
-
-
144
then: 0
else: 144
size = 6 if size == :half
-
-
144
then: 24
if query.nil?
-
24
then: 24
else: 0
size == :full ? "basis-full" : "basis-#{size}/12"
-
else: 120
else
-
120
then: 24
else: 96
size == :full ? "#{query}:basis-full" : "#{query}:basis-#{size}/12"
-
end
-
end
-
end
-
end
-
end
-
48
<%= tag.div(class: classes) do %>
-
24
<%= content %>
-
<% end %>
-
1
module LooposUi
-
1
class FloatBar < LoopComponent
-
1
option :count, default: -> { 0 }
-
1
option :input_name, Types::Coercible::String, optional: true # JS default is "id[]"
-
-
1
renders_many :buttons, types: LooposUi::Button.presets
-
-
# Generate wrapper methods for each button preset
-
# When using the presets, the button will be rendered with the default arguments for a FloatBar
-
1
LooposUi::Button.presets.keys.each do |preset|
-
23
then: 1
else: 22
next if preset == :manual # Manual does not accept arguments
-
-
22
define_method("with_action_#{preset}") do |*args, **kwargs, &block|
-
send("with_button_#{preset}", *args, **with_default_args(**kwargs), &block)
-
end
-
end
-
-
# Manual slots do not accept arguments, simple wrapper
-
1
def with_action_manual(...)
-
with_button_manual(...)
-
end
-
-
1
def with_action(*args, **kwargs, &block)
-
with_button(*args, **with_default_args(**kwargs), &block)
-
end
-
-
1
def with_modal_action(**kwargs, &block)
-
modal = LooposUi::Modal.new(**kwargs)
-
-
# Curious what this does :3 ?
-
modal.define_singleton_method(:with_trigger_button) do |**kwargs, &block|
-
super(**FloatBar.with_default_args(**kwargs), &block)
-
end
-
-
modal.define_singleton_method(:with_primary_action) do |**kwargs, &block|
-
super(
-
**deep_merge_args({ tag_options: FloatBar.action_link_options }, kwargs),
-
&block
-
)
-
end
-
-
modal.define_singleton_method(:with_secondary_action) do |**kwargs, &block|
-
super(
-
**deep_merge_args({ tag_options: FloatBar.action_link_options }, kwargs),
-
&block
-
)
-
end
-
-
with_action_manual do
-
render(modal, &block)
-
end
-
end
-
-
1
def with_popover_action(**kwargs, &block)
-
popover = LooposUi::Popover.new(**kwargs)
-
popover.define_singleton_method(:with_toggle_button) do |**kwargs, &block|
-
super(**FloatBar.with_default_args(**kwargs), &block)
-
end
-
-
with_action_manual do
-
render(popover, &block)
-
end
-
end
-
-
1
def render?
-
buttons.any?
-
end
-
-
1
def before_render
-
check_button_order
-
end
-
-
1
class << self
-
1
def with_default_args(**args)
-
{
-
size: :tiny,
-
type: :tertiary,
-
kind: :neutral,
-
tag_options: action_link_options,
-
}.deep_merge(args)
-
end
-
-
1
def action_link_options
-
{
-
data: { "float-bar-target": "actionLink" },
-
}
-
end
-
end
-
-
1
private
-
-
1
delegate :with_default_args, to: :class
-
-
# detect if buttons kind is always in the following order:
-
# :app, :neutral
-
# TODO: apply this to ActionButtons list
-
1
def check_button_order
-
order = [:app, :neutral]
-
index = 0
-
then: 0
else: 0
button_kinds = buttons.map { |b| b.respond_to?(:kind) ? b.kind : :manual }
-
button_index = 0
-
-
loop do
-
then: 0
else: 0
break if index >= order.size || button_index >= button_kinds.size
-
-
kind = button_kinds[button_index]
-
-
# Manual buttons are ignored and can be placed anywhere
-
then: 0
else: 0
if kind == :manual || kind == order[index]
-
button_index += 1
-
next
-
end
-
-
index += 1
-
end
-
-
then: 0
else: 0
if button_index != button_kinds.size
-
raise <<~ERROR
-
FloatBar buttons are not in the expected order according to the design rules for this component.
-
Invalid element at index #{button_index}, valid values: #{order[order.index(button_kinds[button_index-1])..]}.:
-
Expected:
-
#{order}
-
Got (without kind: :manual, which is always valid):
-
#{button_kinds.reject { |k| k == :manual }}
-
Real (including kind: :manual):
-
#{button_kinds}
-
Check the kind of the buttons in the FloatBar component.
-
ERROR
-
end
-
end
-
end
-
end
-
<%# Turbo prefetch is disabled because the apps have actions that make GET requests, which is not the way %>
-
<%= tag.div(
-
class: "lui-float_bar",
-
data: {
-
controller: "float-bar",
-
turbo_prefetch: false,
-
float_bar_select_all_text_value: t('.select_all'),
-
float_bar_deselect_all_text_value: t('.deselect_all'),
-
float_bar_ids_input_name_value: input_name.presence,
-
}.compact,
-
) do %>
-
<span class="lui-float_bar__count">
-
<%= tag.span(count, data: { float_bar_target: "counter" }) %>
-
<%= tag.span(t('.selected')) %>
-
</span>
-
<span>
-
<%= render LooposUi::ActionButtons.new do |ab| %>
-
<% ab.with_button_group do |bg| %>
-
<% bg.with_button(**with_default_args(text: t('.select_all'), size: :tiny, tag_options: {
-
data: { action: "click->float-bar#selectAll", "float-bar-target": "actionLink selectAllButton" }
-
})) %>
-
<% end %>
-
<% ab.with_button_group do |bg| %>
-
<% buttons.each do |button| %>
-
<% bg.with_button_manual do %>
-
<span data-float-bar-target="action">
-
<%= button %>
-
</span>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</span>
-
<% end %>
-
1
module LooposUi
-
1
module Flows
-
1
class DrawerBar < LoopComponent
-
1
include Pagy::Backend
-
-
1
option :block
-
1
option :dropkiq_scope
-
1
option :description
-
1
option :view_items, default: -> { true }
-
1
option :view_app_settings, default: -> { false }
-
1
option :view_block_settings, default: -> { false }
-
1
option :app_url, default: -> { nil }
-
-
1
private
-
-
1
def app_block?
-
@app_block ||= [
-
"core",
-
"submission",
-
"hubs",
-
"handling",
-
"validation",
-
].include?(block.block_type)
-
end
-
-
1
def ai_decision_block?
-
block.type == "Flows::Blocks::V2::Decision::Ai"
-
end
-
-
1
def exits_block?
-
block.type == "Flows::Blocks::V1::Shopify" || block.type == "Flows::Blocks::V1::Mirakl"
-
end
-
-
1
def header_image_or_icon_args
-
then: 0
if app_block? || ai_decision_block? || exits_block?
-
{
-
svg: helpers.app_svg(app: block.block_type, width: 20),
-
}
-
else: 0
else
-
{
-
icon: block.kind_class.gsub("fa-solid", "fa-regular"),
-
}
-
end
-
end
-
-
1
def app_instance_button
-
LooposUi::Button.new(
-
icon: "arrow-up-right-from-square",
-
type: :tertiary,
-
kind: :neutral,
-
size: :tiny,
-
href: app_url,
-
tag_options: { target: "_blank" },
-
)
-
end
-
-
1
def states
-
::Item.unscoped { block.items.select(:current_flow_token).distinct }.pluck(:current_flow_token)
-
end
-
-
1
def table_args(state)
-
{
-
id: "table-#{dom_id(block)}_#{state}",
-
fetch_url: helpers.main_app.admin_v2_flows_block_url(block, state: state),
-
columns: columns_for_state(state),
-
show_pagination_size_changer: false,
-
show_header: false,
-
}
-
end
-
-
1
def items_and_pagy_for_state(state)
-
items = block.items.includes(:logs).where(current_flow_token: state)
-
then: 0
else: 0
items = items.search_by_full_id(params[:q][:search]) if params.dig(:q, :search).present?
-
-
_pagy, items = pagy(items, items: 5)
-
-
[items, _pagy]
-
rescue Pagy::OverflowError => _e
-
# Assume this is not the wanted table. We cant know for now, because the table
-
# doesn't send _which_ table made the request
-
[[], nil]
-
end
-
-
1
def search_request?
-
params.dig(:q, :search).present? ||
-
params.dig(:page).present? ||
-
params.dig(:per_page).present?
-
end
-
-
1
def columns_for_state(state)
-
[
-
{
-
title: state.to_s.humanize,
-
dataIndex: :item_slug,
-
key: :item_slug,
-
},
-
{
-
title: "Updated at",
-
dataIndex: :updated_at,
-
key: :updated_at,
-
},
-
]
-
end
-
end
-
end
-
end
-
<%= render LooposUi::DrawerBar.new do |drawer| %>
-
<% drawer.with_header do %>
-
<%= render LooposUi::DrawerBar::Header.new do |header|%>
-
<% header.with_corner_actions do %>
-
<%= render "shared/translations_for_model", model: block %>
-
<% end %>
-
<% entity_title = capture do %>
-
<div class="flex items-center gap-2">
-
<span><%= block.translated_block_type %></span>
-
then: 0
else: 0
<% if app_block? && app_url.present? %>
-
<%= render app_instance_button %>
-
<% end %>
-
</div>
-
<% end %>
-
<%= render LooposUi::DrawerBar::Entity.new(
-
title: entity_title,
-
description: block.translated_category,
-
**header_image_or_icon_args
-
)%>
-
<% end %>
-
<% end %>
-
<% drawer.with_header do %>
-
<% title = capture do %>
-
<div class="-ml-2 w-full">
-
<%= tag.turbo_frame id: "#{dom_id(block)}_drawer_name_form" do %>
-
<%= form_with(id: "#{dom_id(block)}_name_form", model: [:admin_v2, block.becomes(::Flows::Block)], class: "w-full") do |form| %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "block[name]",
-
value: block.name,
-
)%>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<div class="px-4 pb-4">
-
<%= render LooposUi::TitleDescription.new(
-
title: title,
-
description: description
-
) %>
-
</div>
-
<% end %>
-
<%= render LooposUi::TabsLayout.new(keep_tab_in_url: false) do |layout| %>
-
then: 0
else: 0
<% layout.with_tab(title: t(".items")) do %>
-
<div class="px-4">
-
<%= tag.turbo_frame id: turbo_id(block, :items) do %>
-
then: 0
<% if block.items.any? %>
-
<% states.each do |state| %>
-
<% items, pagy = items_and_pagy_for_state(state) %>
-
then: 0
else: 0
<% if items.any? || search_request? %>
-
<% token_labels = block.token_labels %>
-
<%= render LooposUi::Accordion.new(title: "#{token_labels[state]} (#{pagy.count})") do |acc| %>
-
<% acc.with_body do %>
-
<%= render LooposUi::V2::Table.new(pagy: pagy, **table_args(state)) do |table| %>
-
<% items.each do |item| %>
-
<% table.with_row(key: item.id) do |row| %>
-
<% row.with_cell(property: :item_slug) do %>
-
<%= render LooposUi::Entities::Item.new(
-
item: item, url: helpers.main_app.edit_admin_v2_item_path(item)
-
) %>
-
<% end %>
-
<% row.with_cell(property: :updated_at) do %>
-
then: 0
else: 0
<%= render LooposUi::DateShow.new(date: item.logs.first.created_at) if item.logs.any? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<div class="text-center copy-14 text-gray-700">
-
<%= t(".no_items") %>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
<% end if view_items %>
-
then: 0
else: 0
<% layout.with_tab(title: t(".block_settings")) do %>
-
<div class="px-4">
-
<%# TODO: move the partials inside LoopOS UI %>
-
<%= render partial: "admin/v2/flows/flows/tabs/block_settings", locals: { block: block, dropkiq_scope: dropkiq_scope }%>
-
<div class="text-right text-xs text-gray-500 mt-4">
-
<%= "#{block.id} - #{block.slug}" %>
-
</div>
-
</div>
-
<% end if view_block_settings %>
-
then: 0
else: 0
<% layout.with_tab(title: "Test") do %>
-
<div class="px-4">
-
<%= render partial: "admin/v2/flows/flows/tabs/test_block", locals: { block: block, dropkiq_scope: dropkiq_scope }%>
-
</div>
-
<% end if ai_decision_block? %>
-
then: 0
else: 0
<% layout.with_tab(title: t(".app_settings")) do %>
-
<div class="px-4">
-
<%= render partial: "admin/v2/flows/flows/tabs/app_settings",
-
locals: { block: block, dropkiq_scope: dropkiq_scope }
-
%>
-
</div>
-
<% end if app_block? && view_app_settings %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class FormEntry < LoopComponent
-
1
renders_one :input
-
1
renders_one :icon_tooltip
-
-
1
option :label
-
1
option :required, default: -> { false }
-
1
option :orientation,
-
Types::Coercible::Symbol.enum(
-
:vertical, :horizontal
-
),
-
default: -> { :vertical }
-
1
option :tooltip, optional: true
-
1
option :label_width, optional: true
-
end
-
end
-
<div class="lui-form_entry lui-form_entry--<%= orientation %>">
-
<div class="lui-form_entry__input">
-
<%= input %>
-
</div>
-
then: 0
else: 0
then: 0
else: 0
<div class="lui-form_entry__label-wrapper <%= orientation == :horizontal ? "pt-2" : "pl-2" %>" style="<%= label_width ? "width: #{label_width}px; min-width: #{label_width}px; word-break: break-word;" : "" %>">
-
<span class="lui-form_entry__label"><%= label %></span>
-
then: 0
else: 0
<span class="lui-form_entry__required"><%= required ? "*" : "" %></span>
-
<div class="lui-form_entry__icon">
-
then: 0
<% if tooltip.present? %>
-
<%= render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", text: tooltip, size: "12") %>
-
else: 0
<% else %>
-
<%= icon_tooltip %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class FormEntryAi < LoopComponent
-
1
option :title, Types::Coercible::String, default: -> { "LoopOS AI" }
-
1
option :description, Types::Coercible::String, optional: true
-
1
option :placeholder, Types::Coercible::String, optional: true
-
1
option :spinner, Types::Bool, optional: true
-
1
option :max_rows, Types::Coercible::Integer, optional: true
-
end
-
end
-
<div class="lui-ai-prompt">
-
<div class="lui-ai-prompt__header">
-
<div class="lui-ai-prompt__icon-container">
-
<div class="lui-ai-prompt__icon">
-
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
-
<g clip-path="url(#paint0_angular_3875_4242_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0150864 -0.0150864 0 15.9974 16.9131)"><foreignObject x="-1187.4" y="-1187.4" width="2374.8" height="2374.8"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(0, 197, 255, 1) 0deg,rgba(0, 216, 181, 1) 43.4361deg,rgba(255, 203, 51, 1) 107.75deg,rgba(242, 115, 53, 1) 179.299deg,rgba(228, 45, 39, 1) 229.644deg,rgba(128, 68, 255, 1) 283.066deg,rgba(0, 114, 255, 1) 333.657deg,rgba(0, 197, 255, 1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path d="M23.9974 15.9995C19.5774 15.9995 15.9974 12.4195 15.9974 7.99951C15.9974 3.57951 19.5774 -0.000488281 23.9974 -0.000488281H31.9974V7.99951C31.9974 12.4195 28.4174 15.9995 23.9974 15.9995Z" data-figma-gradient-fill="{"type":"GRADIENT_ANGULAR","stops":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"stopsVar":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"transform":{"m00":1.8475537702652371e-15,"m01":-30.172840118408203,"m02":31.083795547485352,"m10":30.172840118408203,"m11":1.8475537702652371e-15,"m12":1.8266723155975342},"opacity":1.0,"blendMode":"NORMAL","visible":true}"/>
-
<g clip-path="url(#paint1_angular_3875_4242_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0150864 -0.0150864 0 15.9974 16.9131)"><foreignObject x="-1187.4" y="-1187.4" width="2374.8" height="2374.8"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(0, 197, 255, 1) 0deg,rgba(0, 216, 181, 1) 43.4361deg,rgba(255, 203, 51, 1) 107.75deg,rgba(242, 115, 53, 1) 179.299deg,rgba(228, 45, 39, 1) 229.644deg,rgba(128, 68, 255, 1) 283.066deg,rgba(0, 114, 255, 1) 333.657deg,rgba(0, 197, 255, 1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path d="M15.9974 7.99951C15.9974 12.4195 12.4174 15.9995 7.99738 15.9995C3.57738 15.9995 -0.00262451 12.4195 -0.00262451 7.99951V-0.000488281H7.99738C12.4174 -0.000488281 15.9974 3.57951 15.9974 7.99951Z" data-figma-gradient-fill="{"type":"GRADIENT_ANGULAR","stops":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"stopsVar":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"transform":{"m00":1.8475537702652371e-15,"m01":-30.172840118408203,"m02":31.083795547485352,"m10":30.172840118408203,"m11":1.8475537702652371e-15,"m12":1.8266723155975342},"opacity":1.0,"blendMode":"NORMAL","visible":true}"/>
-
<g clip-path="url(#paint2_angular_3875_4242_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0150864 -0.0150864 0 15.9974 16.9131)"><foreignObject x="-1187.4" y="-1187.4" width="2374.8" height="2374.8"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(0, 197, 255, 1) 0deg,rgba(0, 216, 181, 1) 43.4361deg,rgba(255, 203, 51, 1) 107.75deg,rgba(242, 115, 53, 1) 179.299deg,rgba(228, 45, 39, 1) 229.644deg,rgba(128, 68, 255, 1) 283.066deg,rgba(0, 114, 255, 1) 333.657deg,rgba(0, 197, 255, 1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path d="M7.99738 15.9995C12.4174 15.9995 15.9974 19.5795 15.9974 23.9995C15.9974 28.4195 12.4174 31.9995 7.99738 31.9995H-0.00262451V23.9995C-0.00262451 19.5795 3.57738 15.9995 7.99738 15.9995Z" data-figma-gradient-fill="{"type":"GRADIENT_ANGULAR","stops":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"stopsVar":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"transform":{"m00":1.8475537702652371e-15,"m01":-30.172840118408203,"m02":31.083795547485352,"m10":30.172840118408203,"m11":1.8475537702652371e-15,"m12":1.8266723155975342},"opacity":1.0,"blendMode":"NORMAL","visible":true}"/>
-
<g clip-path="url(#paint3_angular_3875_4242_clip_path)" data-figma-skip-parse="true"><g transform="matrix(0 0.0150864 -0.0150864 0 15.9974 16.9131)"><foreignObject x="-1187.4" y="-1187.4" width="2374.8" height="2374.8"><div xmlns="http://www.w3.org/1999/xhtml" style="background:conic-gradient(from 90deg,rgba(0, 197, 255, 1) 0deg,rgba(0, 216, 181, 1) 43.4361deg,rgba(255, 203, 51, 1) 107.75deg,rgba(242, 115, 53, 1) 179.299deg,rgba(228, 45, 39, 1) 229.644deg,rgba(128, 68, 255, 1) 283.066deg,rgba(0, 114, 255, 1) 333.657deg,rgba(0, 197, 255, 1) 360deg);height:100%;width:100%;opacity:1"></div></foreignObject></g></g><path d="M15.9974 23.9995C15.9974 19.5795 19.5774 15.9995 23.9974 15.9995C28.4174 15.9995 31.9974 19.5795 31.9974 23.9995V31.9995H23.9974C19.5774 31.9995 15.9974 28.4195 15.9974 23.9995Z" data-figma-gradient-fill="{"type":"GRADIENT_ANGULAR","stops":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"stopsVar":[{"color":{"r":0.0,"g":0.84705883264541626,"b":0.70980393886566162,"a":1.0},"position":0.12065579742193222},{"color":{"r":1.0,"g":0.79607844352722168,"b":0.20000000298023224,"a":1.0},"position":0.29930534958839417},{"color":{"r":0.94901961088180542,"g":0.45098039507865906,"b":0.20784313976764679,"a":1.0},"position":0.49805253744125366},{"color":{"r":0.89411765336990356,"g":0.17647059261798859,"b":0.15294118225574493,"a":1.0},"position":0.63789874315261841},{"color":{"r":0.50196081399917603,"g":0.26666668057441711,"b":1.0,"a":1.0},"position":0.78629583120346069},{"color":{"r":0.0,"g":0.44705882668495178,"b":1.0,"a":1.0},"position":0.92682510614395142},{"color":{"r":0.0,"g":0.77254903316497803,"b":1.0,"a":1.0},"position":1.0}],"transform":{"m00":1.8475537702652371e-15,"m01":-30.172840118408203,"m02":31.083795547485352,"m10":30.172840118408203,"m11":1.8475537702652371e-15,"m12":1.8266723155975342},"opacity":1.0,"blendMode":"NORMAL","visible":true}"/>
-
<defs><clipPath id="paint0_angular_3875_4242_clip_path"><path d="M23.9974 15.9995C19.5774 15.9995 15.9974 12.4195 15.9974 7.99951C15.9974 3.57951 19.5774 -0.000488281 23.9974 -0.000488281H31.9974V7.99951C31.9974 12.4195 28.4174 15.9995 23.9974 15.9995Z"/></clipPath><clipPath id="paint1_angular_3875_4242_clip_path"><path d="M15.9974 7.99951C15.9974 12.4195 12.4174 15.9995 7.99738 15.9995C3.57738 15.9995 -0.00262451 12.4195 -0.00262451 7.99951V-0.000488281H7.99738C12.4174 -0.000488281 15.9974 3.57951 15.9974 7.99951Z"/></clipPath><clipPath id="paint2_angular_3875_4242_clip_path"><path d="M7.99738 15.9995C12.4174 15.9995 15.9974 19.5795 15.9974 23.9995C15.9974 28.4195 12.4174 31.9995 7.99738 31.9995H-0.00262451V23.9995C-0.00262451 19.5795 3.57738 15.9995 7.99738 15.9995Z"/></clipPath><clipPath id="paint3_angular_3875_4242_clip_path"><path d="M15.9974 23.9995C15.9974 19.5795 19.5774 15.9995 23.9974 15.9995C28.4174 15.9995 31.9974 19.5795 31.9974 23.9995V31.9995H23.9974C19.5774 31.9995 15.9974 28.4195 15.9974 23.9995Z"/></clipPath></defs>
-
</svg>
-
</div>
-
</div>
-
<div class="lui-ai-prompt__title-desc">
-
<div class="lui-ai-prompt__title-row">
-
<p class="lui-ai-prompt__title">
-
<%= title %>
-
</p>
-
<div class="lui-ai-prompt__state-label">
-
<%= render LooposUi::StateLabel.new(
-
text: "Beta",
-
color: "neutral",
-
) %>
-
</div>
-
</div>
-
<p class="lui-ai-prompt__description">
-
<%= description || t(".description") %>
-
</p>
-
</div>
-
</div>
-
<div class="lui-ai-prompt__input-row">
-
<%= render LooposUi::Inputs::Ai.new(
-
**{
-
placeholder: placeholder,
-
spinner: spinner,
-
max_rows: max_rows
-
}.compact
-
) %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class GridLayout < LoopComponent
-
1
renders_many :sections, ->(**_kwargs) do
-
LooposUi::GridLayout::Section.new
-
end
-
-
1
option :cols, Types::Integer, default: -> { 2 }
-
1
option :gap, Types::Integer, default: -> { 0 }
-
-
# # Responsive options
-
# option :responsive_cols, Types::Hash, default: -> { { sm: 2, md: 3, lg: 4, xl: 5, xxl: 6 } }
-
-
1
def grid_style
-
<<~CSS
-
--grid-cols: #{cols};
-
--grid-gap: #{gap}px;
-
CSS
-
# --grid-cols-sm: #{responsive_cols[:sm]};
-
# --grid-cols-md: #{responsive_cols[:md]};
-
# --grid-cols-lg: #{responsive_cols[:lg]};
-
# --grid-cols-xl: #{responsive_cols[:xl]};
-
# --grid-cols-xxl: #{responsive_cols[:xxl]};
-
end
-
-
1
class Section < LoopComponent
-
end
-
end
-
end
-
<div class="lui-grid_layout" style="<%= grid_style %>">
-
<% sections.each do |section| %>
-
<%= section %>
-
<% end %>
-
</div>
-
<%= tag.div(class: classes) do %>
-
<%= content %>
-
<% end %>
-
1
module LooposUi
-
1
class GroupAvatar < LoopComponent
-
1
option :main_avatar, Types::String | Types::Symbol.enum(:default)
-
1
option :secondary_avatar, Types::String | Types::Symbol.enum(:default)
-
end
-
end
-
<%= tag.div(class: "lui-group_avatar") do %>
-
<%= render LooposUi::Avatar.new(image_url: main_avatar, size: :xl, type: :square) %>
-
<%= tag.div(class: "lui-group_avatar__secondary") do %>
-
<%= render LooposUi::Avatar.new(image_url: secondary_avatar, size: :medium, type: :circle) %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class Header < LoopComponent
-
1
option :icon, optional: true
-
19
option :icon_args, Types::Hash, default: -> { {} }, optional: true
-
19
option :icon_kind, Types::Coercible::Symbol.enum(:informative, :success, :warning, :error), default: -> { :informative }
-
1
option :title, optional: true
-
1
option :description, optional: true
-
1
option :description_icon, optional: true
-
13
option :size, Types::Coercible::Symbol.enum(:page, :tiny, :small, :medium, :large), default: -> { :page }
-
1
mod :size
-
1
option :tooltip, optional: true
-
1
option :count, optional: true
-
1
option :app_instance, optional: true
-
13
option :gapless, Types::Bool, default: -> { false }
-
1
mod :gapless
-
-
1
renders_one :custom_description
-
1
renders_one :info
-
1
renders_one :token_zone
-
1
renders_one :title_token_zone
-
1
renders_one :input_title
-
1
renders_one :semantic_icon, ->(*args, **kwargs) {
-
LooposUi::MIcon.new(*args, **kwargs, size: icon_size)
-
}
-
-
1
def semantic_icon_semantic
-
else: 0
then: 0
return unless semantic_icon?
-
-
semantic_icon.semantic
-
end
-
-
1
private
-
-
1
def icon_size
-
then: 0
else: 0
[:small, :tiny].include?(size) ? 14 : 16
-
end
-
-
1
def icon_class
-
"lui-header__icon lui-header__icon__#{semantic_icon_semantic} lui-header__icon--#{size}"
-
end
-
end
-
end
-
36
<%= tag.div class: classes do %>
-
then: 0
else: 18
<%= tag.div class: "lui-header__info" do %>
-
<%= info %>
-
18
<% end if info? %>
-
36
<%= tag.div class: "lui-header__title_container" do %>
-
18
then: 0
else: 18
<%= render LooposUi::AppLogo.new(app: app_instance) if app_instance.present? %>
-
18
then: 0
else: 18
<%= render LooposUi::MIcon.new(icon, size: icon_size, kind: icon_kind, tag: :a, href: "https://www.google.com") if icon.present? %>
-
then: 0
else: 18
<%= tag.div class: icon_class do %>
-
<%= semantic_icon %>
-
18
<% end if semantic_icon? %>
-
18
<span class="lui-header__title_container__title">
-
18
then: 0
<% if input_title? %>
-
<%= input_title %>
-
else: 18
<% else %>
-
18
<%= title %>
-
<% end %>
-
18
</span>
-
10
then: 10
else: 8
<%= tag.span class: "flex w-fit" do %>
-
10
<%= render LooposUi::Tooltip.new(title: tooltip) %>
-
10
<%= render LooposUi::MIcon.new(:info) %>
-
18
<% end if tooltip.present? %>
-
18
then: 0
else: 18
<%= render LooposUi::Counter.new(count: count, kind: :neutral, size: :small) if count.present? %>
-
then: 0
else: 18
<%= tag.div class: "lui-header__title_container__token_zone" do %>
-
<%= title_token_zone %>
-
18
<% end if title_token_zone? %>
-
<% end %>
-
28
then: 10
else: 8
<%= tag.div class: "lui-header__token_zone" do %>
-
10
<%= token_zone %>
-
18
<% end if token_zone? %>
-
18
then: 10
else: 8
<% if description.present? %>
-
10
<span class="lui-header__description">
-
10
<%= description %>
-
10
then: 10
else: 0
<%= render LooposUi::MIcon.new(description_icon) if description_icon.present? %>
-
</span>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class HeaderComponent < ViewComponent::Base
-
1
attr_reader :app_instance,
-
:current_user,
-
:layout,
-
:environment_name,
-
:additional_list_items,
-
:partnable_grouped_applications,
-
:selected_partnable,
-
:the_loop_main_logo_file,
-
:loopos_manager_avatar_file,
-
:current_manager_version,
-
:me_admin_user_url
-
-
1
def initialize
-
@app_instance = OpenStruct.new
-
@current_user = OpenStruct.new(avatar_url: "loopos-icon.png")
-
@layout = OpenStruct.new
-
@environment_name = OpenStruct.new
-
@additional_list_items = []
-
@partnable_grouped_applications = []
-
@selected_partnable = FakePartnable.new
-
@the_loop_main_logo_file = "loopos-icon.png"
-
@loopos_manager_avatar_file = "loopos-icon.png"
-
@current_manager_version = "0.0.0"
-
@me_admin_user_url = "https://loopos.com"
-
end
-
-
1
def before_render
-
then: 0
else: 0
@app_kind = if params[:app].present?
-
@app_kind = if [
-
"core",
-
"validation",
-
"handling",
-
"hubs",
-
"submission",
-
then: 0
].include?(params[:app])
-
params[:app]
-
else: 0
else
-
then: 0
else: 0
app_instance&.kind
-
end
-
end || "default"
-
end
-
-
1
def app_image_file
-
# @app_kind.present? ? "loopos_ui/#{@app_kind}_logo.png" : "loopos-icon.png"
-
"loopos-icon.png"
-
end
-
-
1
def loopos_logo_file
-
"loopos-icon.png"
-
end
-
-
1
def app_logo_url
-
# app_instance&.app&.logo&.url || "loopos-logo.svg"
-
"loopos-icon.png"
-
end
-
-
1
class PartnableApplicationsComponent < ViewComponent::Base
-
1
with_collection_parameter :partnable_applications
-
-
1
attr_reader :partnable, :applications
-
-
1
def initialize(partnable_applications:)
-
@partnable, @applications = partnable_applications
-
end
-
end
-
-
1
class UserMenuComponent < ViewComponent::Base
-
1
attr_reader :current_user,
-
:current_manager_version,
-
:layout,
-
:additional_list_items,
-
:app_instance,
-
:me_admin_user_url,
-
:powered_by_logo
-
-
1
def initialize(current_user:, current_manager_version:, layout:, additional_list_items:, app_instance:,
-
me_admin_user_url:)
-
@current_user = current_user
-
@current_manager_version = current_manager_version
-
@layout = layout
-
@additional_list_items = additional_list_items
-
@app_instance = app_instance
-
@me_admin_user_url = me_admin_user_url
-
@powered_by_logo = "loopos-icon.png"
-
end
-
end
-
end
-
-
1
class FakePartnable
-
1
def id
-
1
-
end
-
-
1
def name
-
"Fake Partnable"
-
end
-
-
1
def icon_url
-
"loopos-icon.png"
-
end
-
end
-
end
-
then: 0
else: 0
then: 0
else: 0
<% app_kind = ["core", "validation", "handling", "hubs", "submission"].include?(params[:app]) ? params[:app] : app_instance&.kind %>
-
<% app_kind ||= "default" %>
-
-
<div class="los-manager-header">
-
<div class="los-manager-header__relative-wrapper" id="los-manager-relative-wrapper">
-
<div class="los-manager-header__application-menu" id="los-manager-app-menu-open-button">
-
<div class="los-manager-header__application-menu-wrapper">
-
<%= image_tag(image_url('loopOSMenu.svg')) %>
-
</div>
-
</div>
-
<!-- Application Menu -->
-
<div class="los-manager-app-menu" id="los-manager-app-menu">
-
<div class="los-manager-app-menu__header">
-
<div class="los-manager-app-menu__application-menu-header" id="los-manager-app-menu-close-button">
-
<div class="los-manager-header__application-menu-wrapper los-manager-header__application-menu-wrapper--active">
-
<%= image_tag(image_url(app_image_file)) %>
-
</div>
-
</div>
-
<div class="los-manager-app-menu__main-logo">
-
<%= image_tag(image_url(loopos_logo_file), class: 'los-manager-app-menu__image') %>
-
</div>
-
</div>
-
<!-- partners slider -->
-
<div class="los-manager-app-menu_simple-tabs-carousel-container">
-
then: 0
<% if partnable_grouped_applications.length > 1 %>
-
<button class="los-manager-app-menu_tab-btn los-manager-app-menu_tab-previous"><i class="fa-regular fa-chevron-left"></i></button>
-
else: 0
<% else %>
-
<div class="los-manager-app-menu_tab-btn--empty"></div>
-
<% end %>
-
<div class="los-manager-app-menu_tabs-container">
-
<li class="los-manager-app-menu_tab active">
-
then: 0
else: 0
<div class="los-manager-avatar los-manager-avatar--large" data-partner-apps="partner_apps_<%= selected_partnable&.id%>">
-
then: 0
else: 0
<%= image_tag(image_url(selected_partnable&.icon_url || "default-icon.png")) %>
-
</div>
-
then: 0
else: 0
<p><%= selected_partnable&.name %></p>
-
</li>
-
<div class="los-manager-app-menu_simple-tabs-wrapper">
-
<ul class="los-manager-app-menu_simple-tabs">
-
<% partnable_grouped_applications.each_with_index do |partnable_applications, index| %>
-
then: 0
else: 0
<% if partnable_grouped_applications.length > 1 %>
-
<% partnable, applications = partnable_applications %>
-
then: 0
else: 0
<% if partnable != selected_partnable %>
-
<li class="los-manager-app-menu_tab">
-
then: 0
else: 0
<div class="los-manager-avatar los-manager-avatar--large " data-partner-apps="partner_apps_<%= partnable&.id%>">
-
then: 0
else: 0
<%= image_tag(image_url(partnable&.icon_url || "default-icon.png")) %>
-
</div>
-
then: 0
else: 0
<p><%= partnable&.name %></p>
-
</li>
-
<% end %>
-
<% end %>
-
<% end %>
-
</ul>
-
</div>
-
</div>
-
then: 0
<% if partnable_grouped_applications.length > 1 %>
-
<button class="los-manager-app-menu_tab-btn los-manager-app-menu_tab-next"><i class="fa-regular fa-chevron-right"></i></button>
-
else: 0
<% else %>
-
<div class="los-manager-app-menu_tab-btn--empty"></div>
-
<% end %>
-
</div>
-
<div class="scrollable-content" style= "max-height: 350px; overflow-y: auto; overscroll-behavior-y: contain">
-
<%= render PartnableApplicationsComponent.with_collection(partnable_grouped_applications, layout: layout) %>
-
</div>
-
<div class="los-manager-app-menu__footer los-manager-menu-footer">
-
<div class="los-manager-menu-footer__main-logo">
-
<%= image_tag(image_url(the_loop_main_logo_file), class: 'los-manager-menu-footer__image') %>
-
</div>
-
<div class="los-manager-menu-footer__app-version">
-
<div class="los-manager-avatar los-manager-avatar--large">
-
then: 0
else: 0
then: 0
else: 0
then: 0
<% if app_instance&.app&.icon.present? %>
-
<%= image_tag(image_url(app_instance.app.icon.url)) %>
-
else: 0
<% else %>
-
then: 0
<% if app_instance.nil?%> <%# Manager rendering this for itself %>
-
<%= image_tag(image_url("loopos-icon.png")) %>
-
<% else %>
-
else: 0
<%= image_tag(image_url(loopos_manager_avatar_file)) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% if app_instance.present? %>
-
then: 0
<div class="los-manager-menu-content__info">
-
<p class="los-manager-menu-footer__id">id_<%= app_instance.id %></p>
-
<span class="los-manager-menu-footer__version">v_<%= app_instance.current_deployed_version %></span>
-
</div>
-
<% else %>
-
else: 0
<div class="los-manager-menu-content__info">
-
<span class="los-manager-menu-footer__version">v_<%= current_manager_version %></span>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
</div>
-
<div class="los-manager-header__topbar los-manager-topbar">
-
<div class="los-manager-topbar__brand los-manager-brand">
-
<% if layout == "zeroo" %>
-
then: 0
<div class="los-manager-topbar__brand-wrapper">
-
<%= image_tag(image_url('logo-zero.svg'), class: 'los-manager-brand__image') %>
-
<div class="los-manager-topbar__brand-spliter"></div>
-
<%= image_tag(image_url(partner.icon_url), class: 'los-manager-brand__image' ) %>
-
</div>
-
<% else %>
-
else: 0
<%= link_to app_instance&.app_url do %>
-
then: 0
else: 0
<%= image_tag(image_url(app_logo_url), class:"los-manager-brand__image") %>
-
<% end %>
-
<div class="los-manager-brand__info">
-
<div class="los-manager-topbar-app-instance-wrapper">
-
<% if app_instance&.partner.present? %>
-
then: 0
else: 0
then: 0
else: 0
<div class="los-manager-app-instance">
-
<%= app_instance&.partner.name %>
-
then: 0
else: 0
</div>
-
<% end %>
-
<% if app_instance&.name.present? %>
-
then: 0
else: 0
then: 0
else: 0
<div class="los-manager-app-instance">
-
<%= app_instance&.name %>
-
then: 0
else: 0
</div>
-
<% end %>
-
<% if environment_name&.present? %>
-
then: 0
else: 0
then: 0
else: 0
<div class="los-manager-app-instance">
-
<%= environment_name %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
<%= render UserMenuComponent.new(
-
current_user: current_user,
-
app_instance: app_instance,
-
current_manager_version: current_manager_version,
-
layout: layout,
-
additional_list_items: additional_list_items,
-
me_admin_user_url: me_admin_user_url,
-
) %>
-
</div>
-
</div>
-
-
<%= render "loopos_ui/header_component/header_styles", app: app_instance&.app %>
-
then: 0
else: 0
<div class="los-manager-app-menu__content los-manager-menu-content los-manager-content-applications--hidden" id="partner_apps_<%= partnable&.id%>">
-
<div class="los-manager-menu-content__applications los-manager-content-applications">
-
<%= render partial: "api/v1/general/application_button", locals: { layout: layout }, collection: applications, as: :application %>
-
</div>
-
</div>
-
<div class="los-manager-topbar__user-wrapper">
-
<div class="los-manager-topbar__user los-manager-user" id="los-user-open-menu">
-
<div class="los-manager-avatar los-manager-avatar--normal">
-
<%= image_tag(image_url(current_user.avatar_url), class: 'los-manager-user__avatar-image' ) %>
-
</div>
-
<div class="los-manager-user__info">
-
<p class="los-manager-user__name"><%= current_user.full_name %></p>
-
then: 0
else: 0
<% if current_user.partner.present? %>
-
<div class="los-manager-user__partner">
-
<div class="los-manager-user__partner-image-wrapper">
-
then: 0
else: 0
<%= image_tag(image_url(current_user.partner&.icon_url || "default-icon.png") , class: 'los-manager-user__partner-image-avatar' ) %>
-
</div>
-
then: 0
else: 0
<span class="los-manager-user__partner-name"><%= current_user.partner&.name %></span>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<!-- Start of user Menu -->
-
<div class="los-manager-user-menu-wrapper">
-
<div class="mt-4"></div>
-
<div class="los-manager-user__menu los-manager-user-menu" id="los-manager-user-menu">
-
<div class="los-manager-user-menu__header">
-
<div class="los-manager-avatar los-manager-avatar--xxl">
-
<%= image_tag(image_url(current_user.avatar_url)) %>
-
</div>
-
<div class="los-manager-user__header-info">
-
<p class="los-manager-user__header-name"> <%= I18n.t("header.user.menu.hello")%>, <%= current_user.full_name %></p>
-
<p class="los-manager-user__header-email"><%= current_user.login %></p>
-
then: 0
else: 0
<% if current_user.partner.present? %>
-
<div class="los-manager-user__partner">
-
<div class="los-manager-avatar los-manager-avatar--small">
-
then: 0
else: 0
<%= image_tag(image_url(current_user.partner&.icon_url || "default-icon.png") , class: 'los-manager-user__avatar-image' ) %>
-
</div>
-
then: 0
else: 0
<p class="los-manager-user__partner-name"><%= current_user.partner&.name %></p>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<div class="los-manager-user-menu__content">
-
<div class="los-manager-user-menu__navigation-list los-manager-navigation-list">
-
<h4 class="los-manager-navigation-list__title"> <%= I18n.t("header.user.menu.account")%> </h4>
-
<ul class="los-manager-navigation-list__list">
-
<li style="width:100%">
-
<%# Submission has it's own profile page %>
-
then: 0
else: 0
then: 0
else: 0
<% if app_instance&.kind != "submission" %>
-
<%= link_to I18n.t("header.user.menu.profile"),
-
me_admin_user_url,
-
class: "los-manager-navigation-list__link los-manager-navigation-list__link"
-
%>
-
<% end %>
-
</li>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<% if current_user&.partner&.id.present? && current_user&.can_view_partner? %>
-
<li style="width:100%"><a href="<%= admin_partner_me_url %>" target="_blank" class="los-manager-navigation-list__link los-manager-navigation-list__link"> <%= I18n.t("header.user.menu.partner_profile") %> </a></li>
-
<% end %>
-
-
<% additional_list_items.each do |item| %>
-
<% label, link = item.values_at(:label, :link) %>
-
<li style="width:100%">
-
<a href="<%= link %>" target="_blank" class="los-manager-navigation-list__link los-manager-navigation-list__link"> <%= label %> </a>
-
</li>
-
<% end %>
-
-
<!--<li style="width:100%">
-
<a class="los-manager-navigation-list__link los-manager-navigation-list__link--disabled">
-
<%#= I18n.t("header.user.menu.preferences") %>
-
</a>
-
</li> -->
-
</ul>
-
</div>
-
-
<div class="los-manager-user-menu__navigation-list los-manager-navigation-list">
-
<h4 class="los-manager-navigation-list__title"> <%= I18n.t("header.user.menu.about_us")%> </h4>
-
<ul class="los-manager-navigation-list__list">
-
<li style="width:100%">
-
then: 0
<% if layout == "zeroo" %>
-
<a href="" target="_blank" class="los-manager-navigation-list__link los-manager-navigation-list__link">Zeroo</a>
-
else: 0
<% else %>
-
<a href="https://loop-os.pt/" target="_blank" class="los-manager-navigation-list__link los-manager-navigation-list__link">LoopOS</a>
-
<% end %>
-
</li>
-
<li style="width:100%"><a href="https://www.theloop.pt/" target="_blank" class="los-manager-navigation-list__link los-manager-navigation-list__link">The Loop Co.</a></li>
-
</ul>
-
</div>
-
</div>
-
<div class="los-manager-user-menu__footer los-manager-user-footer">
-
<div class="los-manager-user-footer__los-brand">
-
<div class="los-manager-user-footer__powered"><span> <%= I18n.t("header.user.menu.powered_by") %> </span> <%= image_tag(image_url(powered_by_logo)) %> </div>
-
<div class="los-manager-user-footer__version"> <%= I18n.t("header.user.menu.version") %> <%= current_manager_version %> </div>
-
</div>
-
<div class="los-manager-user-footer__logout">
-
<div class="los-manager-button los-manager-button--danger" id="logout-button"><i class="fa-regular fa-arrow-right-from-bracket"></i> <%= I18n.t("header.logout") %> </div>
-
</div>
-
</div>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class HistoryCard < LoopComponent
-
1
option :proposals_data
-
end
-
end
-
-
<%= react_component(
-
"HistoryCard", { proposalsData: proposals_data, locale: I18n.locale }
-
)%>
-
# frozen_string_literal: true
-
-
1
require "json"
-
-
1
module LooposUi
-
1
class HumanView < LoopComponent
-
1
ICON_BEHAVIORS = ["rotate", "swap"].freeze
-
-
1
option :title, type: Types::String.optional, optional: true
-
1
option :data, type: Types::Hash | Types::Array | Types::String
-
1
option :tree_icon_behavior, type: Types::String, default: -> { "rotate" }
-
1
option :tree_icon_closed, type: Types::String, default: -> { "fa-chevron-right" }
-
1
option :tree_icon_open, type: Types::String, default: -> { "fa-chevron-down" }
-
-
1
def parsed_data
-
6
@parsed_data ||= parse_data(data)
-
end
-
-
1
def tree_children(value = parsed_data)
-
2
case value
-
when: 2
when Hash
-
2
value
-
when: 0
when Array
-
value.each_with_index.map { |child, index| ["[#{index}]", child] }
-
else: 0
else
-
[]
-
end
-
end
-
-
1
def formatted_value(value)
-
then: 0
else: 0
return "null" if value.nil?
-
-
value.to_s
-
end
-
-
1
def resolved_tree_icon_behavior
-
2
then: 2
else: 0
ICON_BEHAVIORS.include?(tree_icon_behavior) ? tree_icon_behavior : "rotate"
-
end
-
-
1
private
-
-
1
def parse_data(value)
-
2
then: 2
else: 0
return value if value.is_a?(Hash) || value.is_a?(Array)
-
then: 0
else: 0
return {} if value.blank?
-
-
JSON.parse(value.to_s)
-
rescue JSON::ParserError, TypeError
-
value
-
end
-
end
-
end
-
2
<%= tag.div(
-
class: "lui-human_view",
-
data: {
-
controller: "human-view",
-
human_view_icon_behavior_value: resolved_tree_icon_behavior
-
}
-
2
) do %>
-
<div class="lui-human_view__content">
-
2
then: 0
<% if parsed_data.blank? %>
-
else: 2
<span class="lui-human_view__empty"><%= t("loopos_ui.human_view.empty") %></span>
-
2
then: 2
<% elsif parsed_data.is_a?(Hash) || parsed_data.is_a?(Array) %>
-
2
<% tree_children.each do |child_key, child_value| %>
-
2
<%= render(LooposUi::HumanView::TreeNode.new(
-
node_key: child_key,
-
node_value: child_value,
-
tree_icon_closed: tree_icon_closed,
-
tree_icon_open: tree_icon_open
-
2
)) %>
-
<% end %>
-
else: 0
<% else %>
-
then: 0
else: 0
<% if title.present? %>
-
<span class="lui-human_view__title"><%= title %>:</span>
-
<% end %>
-
<span class="lui-human_view__primitive_value"><%= formatted_value(parsed_data) %></span>
-
<% end %>
-
2
</div>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class HumanView
-
1
class TreeNode < LoopComponent
-
1
option :node_key
-
1
option :node_value
-
1
option :tree_icon_closed, type: Types::String, default: -> { "fa-chevron-right" }
-
1
option :tree_icon_open, type: Types::String, default: -> { "fa-chevron-down" }
-
-
1
def node_key_label
-
18
node_key.to_s
-
end
-
-
1
def children
-
24
case node_value
-
when: 8
when Hash
-
32
node_value.sort_by { |key, _| key.to_s }
-
when: 4
when Array
-
12
node_value.each_with_index.map { |child, index| [(index + 1).to_s, child] }
-
else: 12
else
-
12
[]
-
end
-
end
-
-
1
def value_color_class
-
12
when: 4
case node_value
-
4
when: 6
when String then "lui-human_view-tree_node__value--string"
-
6
when: 0
when Numeric then "lui-human_view-tree_node__value--number"
-
when NilClass then "lui-human_view-tree_node__value--null"
-
else: 2
else
-
2
"lui-human_view-tree_node__value--default"
-
end
-
end
-
-
1
def formatted_value
-
12
then: 0
else: 12
return "null" if node_value.nil?
-
-
12
node_value.to_s
-
end
-
end
-
end
-
end
-
18
<div class="lui-human_view-tree_node">
-
18
then: 6
<% if children.any? %>
-
6
<% content_id = "extra-data-tree-#{SecureRandom.hex(6)}" %>
-
6
<div class="lui-human_view-tree_node__branch">
-
<button
-
type="button"
-
class="lui-human_view-tree_node__trigger"
-
data-action="click->human-view#toggleTree"
-
aria-expanded="false"
-
6
aria-controls="<%= content_id %>">
-
<i
-
data-tree-icon
-
6
data-tree-icon-closed="<%= tree_icon_closed %>"
-
6
data-tree-icon-open="<%= tree_icon_open %>"
-
6
class="fa-solid <%= tree_icon_closed %> lui-human_view-tree_node__icon"></i>
-
6
<span class="lui-human_view-tree_node__key"><%= node_key_label %></span>
-
</button>
-
-
6
<div id="<%= content_id %>" data-tree-content data-state="closed" class="lui-human_view-tree_node__children" hidden>
-
6
<% children.each do |child_key, child_value| %>
-
16
<%= render(LooposUi::HumanView::TreeNode.new(
-
node_key: child_key,
-
node_value: child_value,
-
tree_icon_closed: tree_icon_closed,
-
tree_icon_open: tree_icon_open
-
16
)) %>
-
<% end %>
-
6
</div>
-
</div>
-
else: 12
<% else %>
-
12
<div class="lui-human_view-tree_node__leaf">
-
12
<i class="fa-solid <%= tree_icon_closed %> lui-human_view-tree_node__icon lui-human_view-tree_node__icon--placeholder"></i>
-
12
<span class="lui-human_view-tree_node__key"><%= node_key_label %>:</span>
-
12
<span class="lui-human_view-tree_node__value <%= value_color_class %>"><%= formatted_value %></span>
-
</div>
-
<% end %>
-
18
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Icon < LoopComponent
-
1
include FaviconAware
-
-
1
option :icon, type: Types::Coercible::String
-
1
option :count, optional: true
-
1
option :size, default: -> { "16" }, type: Types::Coercible::Integer
-
3
option :color, default: -> { :black }, type: Types::Coercible::String
-
-
1
def initialize(...)
-
12
super
-
-
12
@icon = faviconize(icon)
-
end
-
-
1
def styles
-
12
<<~CSS.squish
-
font-size: #{size}px;
-
color: #{color};
-
CSS
-
end
-
end
-
end
-
24
<%= tag.div(class: "lui-icon h-[#{size}px] w-[#{size}px]") do %>
-
12
then: 12
else: 0
<%= tag.i(class: "#{icon} lui-icon__icon" , style: styles) if icon.present? %>
-
12
then: 0
else: 12
<%= render LooposUi::Counter.new(count: count, kind: :neutral) if count%>
-
<% end %>
-
1
module LooposUi
-
1
class IconTooltip < LoopComponent
-
1
renders_one :custom_tooltip
-
-
1
def initialize(app: nil, icon: nil, count: nil, size: nil, text: nil, icon_color: nil)
-
@app = app
-
@icon = icon
-
@count = count
-
@text = text
-
then: 0
else: 0
@size = size.presence || (@app.present? ? "small" : "16")
-
@icon_color = icon_color
-
end
-
end
-
end
-
then: 0
<% if @app.present? %>
-
<span class="lui-icon_tooltip">
-
then: 0
<% if custom_tooltip %>
-
<%= custom_tooltip %>
-
else: 0
<% else %>
-
<%= render LooposUi::Tooltip.new(title: @text, position: :top) %>
-
<% end %>
-
<%= render LooposUi::Logo.new(app: @app, count: @count, size: @size, icon: true ) %>
-
else: 0
</span>
-
then: 0
else: 0
<% elsif @icon.present? %>
-
<span class="lui-icon_tooltip">
-
then: 0
<% if custom_tooltip %>
-
<%= custom_tooltip %>
-
else: 0
<% else %>
-
<%= render LooposUi::Tooltip.new(title: @text, position: :top) %>
-
<% end %>
-
<%= render LooposUi::Icon.new(icon: @icon, count: @count, size: @size, color: @icon_color) %>
-
</span>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class IdentityToken < LooposUi::Token
-
COLORS = {
-
# text, bg
-
1
general: [find_color("general-gray-900"), find_color("general-gray-200")], # General/Gray/900, General/Gray/200
-
}
-
-
APP_COLORS = {
-
# text, bg
-
1
manager: [find_color("apps-manager-800-primary"), find_color("apps-manager-300")], # Apps/Manager/800-Primary, Apps/Manager/300
-
core: [find_color("apps-core-800-primary"), find_color("apps-core-300")], # Apps/Core/800-Primary, Apps/Core/300
-
hubs: [find_color("apps-hubs-800-primary"), find_color("apps-hubs-300")], # Apps/Hubs/800-Primary, Apps/Hubs/300
-
}
-
-
1
class << self
-
1
def for_brand(brand)
-
new(
-
text: brand.name,
-
icon: "fa-regular fa-seal",
-
color: :general,
-
)
-
end
-
-
1
def for_category(category)
-
new(
-
text: category.name,
-
icon: "fa-regular fa-grid",
-
color: :general,
-
)
-
end
-
-
1
def for_protocol(protocol)
-
new(
-
text: protocol.name,
-
icon: "fa-regular fa-bars-staggered",
-
color: :app,
-
)
-
end
-
-
1
def for_inherited_protocol(protocol)
-
new(
-
text: protocol.name,
-
leading_icon: "fa-kit fa-regular-bars-staggered-tag",
-
trailing_icon: "fa-regular fa-diagram-nested",
-
color: :app,
-
)
-
end
-
-
1
def for_partnable(partnable)
-
new(
-
text: partnable.name,
-
icon: "fa-regular fa-box",
-
color: :general,
-
)
-
end
-
end
-
-
1
def initialize(**kwargs)
-
@draft = kwargs.delete(:draft)
-
-
super(**kwargs)
-
-
@color ||= :general
-
set_type
-
end
-
-
1
def classes
-
then: 0
else: 0
"#{super} lui-identity-token #{"lui-identity-token-#{@type}"} #{"lui-identity-token--draft" if draft?}".chomp
-
end
-
-
1
def styles
-
<<~CSS.squish
-
color: #{@text_color};
-
background-color: #{@bg_color};
-
CSS
-
end
-
-
1
private
-
-
1
def set_color(color)
-
then: 0
@text_color, @bg_color = if color.present?
-
then: 0
if COLORS.key?(color.to_sym)
-
else: 0
COLORS[color.to_sym]
-
then: 0
elsif color.to_sym == :app
-
APP_COLORS[LooposUi.config.app_type.to_sym] || COLORS[:general]
-
else: 0
else
-
raise ArgumentError, "Invalid color: #{color}, available colors: #{COLORS.keys.join(", ")}, app."
-
end
-
else: 0
else
-
COLORS.excluding(LooposUi.config.app_type.to_sym).values.sample
-
end
-
end
-
-
1
def set_type
-
then: 0
else: 0
@type = case @color&.to_sym
-
when: 0
when :app
-
then: 0
else: 0
APP_COLORS.key?(LooposUi.config.app_type.to_sym) ? LooposUi.config.app_type : :general
-
else: 0
else
-
@color
-
end
-
end
-
-
1
def draft?
-
@draft
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Image < ViewComponent::Base
-
1
attr_reader :resource, :editable, :image_url, :size, :rounded
-
-
1
def initialize(resource: nil, editable: false, image_url: nil, size: "full", rounded: false)
-
@resource = resource
-
@editable = editable
-
@image_url = image_url
-
@size = size
-
@rounded = rounded
-
end
-
-
1
def default_svg
-
<<-SVG.html_safe
-
<svg class="loopui-image__image" width="162" height="162" viewBox="0 0 162 162" fill="none" xmlns="http://www.w3.org/2000/svg">
-
<rect width="162" height="162" rx="8" fill="#DEE2E6"/>
-
<path d="M81 73.5001C81 71.1989 82.8655 69.3334 85.1667 69.3334C87.4679 69.3334 89.3334 71.1989 89.3334 73.5001C89.3334 75.8013 87.4679 77.6667 85.1667 77.6667C82.8655 77.6667 81 75.8013 81 73.5001Z" fill="white"/>
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.5938 62.6667C75.322 62.6667 73.5153 62.6667 72.0577 62.7858C70.565 62.9078 69.2922 63.1629 68.1268 63.7567C66.2452 64.7154 64.7154 66.2452 63.7566 68.1268C63.1628 69.2923 62.9077 70.565 62.7858 72.0577C62.6667 73.5154 62.6667 75.3221 62.6667 77.5938V84.4063C62.6667 86.6781 62.6667 88.4848 62.7858 89.9425C62.9077 91.4351 63.1628 92.7079 63.7566 93.8733C64.7154 95.7549 66.2452 97.2847 68.1268 98.2435C69.2922 98.8373 70.565 99.0924 72.0577 99.2143C73.5153 99.3334 75.3219 99.3334 77.5936 99.3334H84.4063C86.678 99.3334 88.4848 99.3334 89.9424 99.2143C91.4351 99.0924 92.7078 98.8373 93.8733 98.2435C95.7549 97.2847 97.2847 95.7549 98.2434 93.8733C98.8372 92.7079 99.0923 91.4351 99.2143 89.9425C99.3334 88.4848 99.3334 86.6782 99.3334 84.4065V77.5938C99.3334 75.3221 99.3334 73.5153 99.2143 72.0577C99.0923 70.565 98.8372 69.2923 98.2434 68.1268C97.2847 66.2452 95.7549 64.7154 93.8733 63.7567C92.7078 63.1629 91.4351 62.9078 89.9424 62.7858C88.4847 62.6667 86.678 62.6667 84.4063 62.6667H77.5938ZM69.6401 66.7267C70.2573 66.4122 71.0426 66.2132 72.3291 66.1081C73.6351 66.0014 75.3056 66.0001 77.6667 66.0001H84.3334C86.6944 66.0001 88.365 66.0014 89.671 66.1081C90.9575 66.2132 91.7428 66.4122 92.36 66.7267C93.6144 67.3659 94.6342 68.3857 95.2734 69.6401C95.5879 70.2573 95.7869 71.0426 95.892 72.3292C95.9987 73.6351 96 75.3057 96 77.6667V84.3334C96 84.9308 95.9999 85.4839 95.9981 85.9981L93.357 83.3571C92.0553 82.0554 89.9447 82.0554 88.643 83.3571L84.9226 87.0775C84.5972 87.4029 84.0695 87.4029 83.7441 87.0775L73.357 76.6904C72.0553 75.3887 69.9447 75.3887 68.643 76.6904L66 79.3334V77.6667C66 75.3057 66.0013 73.6351 66.108 72.3292C66.2131 71.0426 66.4122 70.2573 66.7266 69.6401C67.3658 68.3857 68.3857 67.3659 69.6401 66.7267Z" fill="white"/>
-
<path d="M81 73.5001C81 71.1989 82.8655 69.3334 85.1667 69.3334C87.4679 69.3334 89.3334 71.1989 89.3334 73.5001C89.3334 75.8013 87.4679 77.6667 85.1667 77.6667C82.8655 77.6667 81 75.8013 81 73.5001Z" stroke="white" stroke-width="1.67"/>
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.5938 62.6667C75.322 62.6667 73.5153 62.6667 72.0577 62.7858C70.565 62.9078 69.2922 63.1629 68.1268 63.7567C66.2452 64.7154 64.7154 66.2452 63.7566 68.1268C63.1628 69.2923 62.9077 70.565 62.7858 72.0577C62.6667 73.5154 62.6667 75.3221 62.6667 77.5938V84.4063C62.6667 86.6781 62.6667 88.4848 62.7858 89.9425C62.9077 91.4351 63.1628 92.7079 63.7566 93.8733C64.7154 95.7549 66.2452 97.2847 68.1268 98.2435C69.2922 98.8373 70.565 99.0924 72.0577 99.2143C73.5153 99.3334 75.3219 99.3334 77.5936 99.3334H84.4063C86.678 99.3334 88.4848 99.3334 89.9424 99.2143C91.4351 99.0924 92.7078 98.8373 93.8733 98.2435C95.7549 97.2847 97.2847 95.7549 98.2434 93.8733C98.8372 92.7079 99.0923 91.4351 99.2143 89.9425C99.3334 88.4848 99.3334 86.6782 99.3334 84.4065V77.5938C99.3334 75.3221 99.3334 73.5153 99.2143 72.0577C99.0923 70.565 98.8372 69.2923 98.2434 68.1268C97.2847 66.2452 95.7549 64.7154 93.8733 63.7567C92.7078 63.1629 91.4351 62.9078 89.9424 62.7858C88.4847 62.6667 86.678 62.6667 84.4063 62.6667H77.5938ZM69.6401 66.7267C70.2573 66.4122 71.0426 66.2132 72.3291 66.1081C73.6351 66.0014 75.3056 66.0001 77.6667 66.0001H84.3334C86.6944 66.0001 88.365 66.0014 89.671 66.1081C90.9575 66.2132 91.7428 66.4122 92.36 66.7267C93.6144 67.3659 94.6342 68.3857 95.2734 69.6401C95.5879 70.2573 95.7869 71.0426 95.892 72.3292C95.9987 73.6351 96 75.3057 96 77.6667V84.3334C96 84.9308 95.9999 85.4839 95.9981 85.9981L93.357 83.3571C92.0553 82.0554 89.9447 82.0554 88.643 83.3571L84.9226 87.0775C84.5972 87.4029 84.0695 87.4029 83.7441 87.0775L73.357 76.6904C72.0553 75.3887 69.9447 75.3887 68.643 76.6904L66 79.3334V77.6667C66 75.3057 66.0013 73.6351 66.108 72.3292C66.2131 71.0426 66.4122 70.2573 66.7266 69.6401C67.3658 68.3857 68.3857 67.3659 69.6401 66.7267Z" stroke="white" stroke-width="1.67"/>
-
</svg>
-
SVG
-
end
-
-
1
def classes
-
then: 0
else: 0
"loopui-image--#{@size} loopui-image--#{@rounded ? "round" : "square"}"
-
end
-
end
-
end
-
<div class="loopui-image group <%= classes %>" data-controller="upload">
-
then: 0
else: 0
then: 0
<%= if @resource&.image_attached?
-
image_tag @resource.image,
-
class: "loopui-image__image",
-
else: 0
data: { upload_target: "image" }
-
then: 0
elsif @image_url.present?
-
image_tag @image_url,
-
class: "loopui-image__image",
-
data: { upload_target: "image" }
-
else: 0
else
-
default_svg
-
end %>
-
then: 0
else: 0
<% if @editable %>
-
<div class="loopui-image__image-edit" data-action="click->upload#openFilePicker">
-
<%= form_for @resource.object, url: @resource.attach_image_path, html: { data: { turbo_frame: "single_image_uploader", upload_target: "form" } } do |f| %>
-
<%= f.label :image, title: 'Upload image',
-
class:'loopui-image__image-edit-label group' do %>
-
<i class="loopui-image__image-edit-icon fa-light fa-upload"></i>
-
<span class="loopui-image__image-edit-text">Upload Image</span>
-
<% end %>
-
<%= f.file_field :image, direct_upload: true, class: "hidden", data: { upload_target: "file", action: "change->upload#previewAndSubmit", direct_upload_url: @resource.direct_upload_url } %>
-
<%= f.submit "Save", class: "hidden" %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class IndexHeader < LoopComponent
-
1
include LooposUi::ResourceAware
-
-
1
renders_many :title_labels, types: {
-
counter: LooposUi::CounterLabel,
-
state: LooposUi::StateLabel,
-
double_state: LooposUi::DoubleStateLabel,
-
}
-
-
1
then: 0
else: 0
option :title, optional: true, default: proc { resource&.model_class_plural_name }
-
1
option :with_counter, default: proc { true }
-
-
1
def before_render
-
then: 0
else: 0
if resource.present? && with_counter?
-
with_title_label_counter(text: resource.label_counter(resource.model_class.count))
-
end
-
end
-
-
1
private
-
-
1
def with_counter?
-
@with_counter
-
end
-
end
-
end
-
<turbo-frame id="lui-index-header" class="lui-index_header">
-
<h1 class="lui-index_header__title">
-
<%= title %>
-
</h1>
-
<div>
-
<% title_labels.each do |label| %>
-
<%= label %>
-
<% end %>
-
</div>
-
</turbo-frame>
-
-
# frozen_string_literal: true
-
-
# TODO: document LookBook
-
1
module LooposUi
-
1
class IndexLayout < LoopComponent
-
1
renders_one :action_bar, ->(**args, &block) { LooposUi::ActionBar.new(**args, current_action: :index, &block) }
-
1
renders_one :header, ->(**args) {
-
case header_type
-
when: 0
when :index
-
LooposUi::IndexHeader.new(**args, model_class: model_class)
-
else: 0
else
-
LooposUi::PageHeader.new(**args, model_class: model_class)
-
end
-
}
-
-
1
option :model_class, optional: true
-
1
option :header_type, Types::Coercible::Symbol.enum(:page, :index), default: -> { :index }
-
1
renders_one :highlight
-
-
1
def before_render
-
then: 0
else: 0
with_header(with_counter: true) if model_class.present? && !header?
-
end
-
end
-
end
-
<div class="lui-index-layout">
-
<%= action_bar %>
-
<%= header %>
-
then: 0
else: 0
<% if highlight? %>
-
<div class="w-full">
-
<%= highlight %>
-
</div>
-
<% end %>
-
<div class="w-full grow">
-
<%= content %>
-
</div>
-
<%# TODO: extract this class to drawer_bar.scss %>
-
<turbo-frame class="lui-show-layout__drawer_wrapper" id="lui-main-layout-drawer_bar" class="fixed top-[106px] right-0" data-controller="drawer-bar">
-
</turbo-frame>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module InlineEditComponent
-
1
def element_edit(element, show_path, edit_path, attribute, type = :text_field, **kwargs, &block)
-
render(
-
LooposUi::InlineEditComponent::Base.new(
-
model: element,
-
attribute: attribute,
-
type: type,
-
show_path: show_path,
-
edit_path: edit_path,
-
**kwargs,
-
),
-
&block
-
)
-
end
-
-
1
class Base < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :show
-
1
renders_one :edit
-
-
1
attr_reader :model,
-
:attribute,
-
:type,
-
:show_path,
-
:edit_path,
-
:with_turbo_wrapper,
-
:custom_class,
-
:size,
-
:has_margin,
-
:custom_turbo_frame_id
-
-
1
def initialize(model:, attribute:, type:, show_path:, edit_path:, with_turbo_wrapper: true, custom_class: "",
-
size: "base", has_margin: false, show_edit_button: true, custom_turbo_frame_id: nil)
-
2
super
-
2
@model = model
-
2
@attribute = attribute
-
2
@type = type
-
2
@show_path = show_path
-
2
@edit_path = edit_path
-
2
@with_turbo_wrapper = with_turbo_wrapper
-
2
@custom_class = custom_class
-
2
@size = size
-
2
@has_margin = has_margin
-
2
@show_edit_button = show_edit_button
-
2
@custom_turbo_frame_id = custom_turbo_frame_id
-
end
-
-
1
def show_action?
-
2
!(edit_action? || keep_inline_edit?)
-
end
-
-
1
def edit_action?
-
2
view_context.action_name == "edit" || (Rails.env.development? && params[:edit_mode])
-
end
-
-
1
def keep_inline_edit?
-
2
params[:keep_inline_edit] == "true"
-
end
-
-
1
def turbo_frame_id
-
2
custom_turbo_frame_id.presence || "inline_edit_#{dom_id(model)}_#{attribute}"
-
end
-
-
1
def form_attributes(form_attrs)
-
then: 0
if form_attrs.is_a?(Array)
-
else: 0
{ model: form_attrs}
-
then: 0
elsif form_attrs.is_a?(String)
-
{ url: form_attrs, method: :patch }
-
else: 0
else
-
raise ArgumentError, "form_attributes must be an Array or a String"
-
end
-
end
-
-
1
def render_edit_button
-
else: 0
then: 0
return unless @show_edit_button
-
-
render(
-
partial: "loopos_ui/edit_button",
-
locals: {
-
path: edit_path,
-
frame_name: turbo_frame_id,
-
extra_classes: "text-[8px]! mt-[-6px]",
-
has_margin: has_margin,
-
size: size,
-
},
-
)
-
end
-
-
1
def render_submit_buttons(form:)
-
render(
-
partial: "loopos_ui/edit_submit",
-
locals: {
-
show_path: show_path,
-
frame_name: turbo_frame_id,
-
form: form,
-
extra_classes: "text-[8px]! mt-[-6px]",
-
extra_wrapper_classes: "!gap-2xs",
-
has_margin: has_margin,
-
size: size,
-
},
-
)
-
end
-
end
-
end
-
end
-
<%# FIXME: looks ugly but it's fast rn %>
-
2
then: 2
else: 0
<%= raw("<turbo-frame id=\"#{turbo_frame_id}\" class=\"#{custom_class}\">") if with_turbo_wrapper %>
-
<div class="flex flex-row gap-2xs">
-
2
then: 2
<% if show_action? %>
-
2
then: 2
<% if show? %>
-
2
<%= show %>
-
<% else %>
-
else: 0
<%# TODO: default inline render sytle %>
-
<%= model.send(attribute) %>
-
<%= render_edit_button %>
-
<% end %>
-
else: 0
<% else %>
-
then: 0
<% if edit?%>
-
<%= edit %>
-
else: 0
<% else %>
-
<%= form_with(model: [:admin, model], class: "w-full") do |form| %>
-
<%= form.send(type, attribute) %>
-
<%= render_submit_buttons(form: form) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
2
</div>
-
2
then: 2
else: 0
<%= raw("</turbo-frame>") if with_turbo_wrapper %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class InlineTextEdit < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
option :model
-
1
option :attribute
-
1
option :form_attrs
-
1
option :editable, default: -> { false }
-
1
option :open_actions, default: -> { false }
-
-
1
private
-
-
1
def turbo_frame_id
-
"inline_text_edit_#{dom_id(model)}_#{attribute}"
-
end
-
-
1
def value
-
model.send(attribute)
-
end
-
end
-
end
-
<%
-
=begin%>
-
<%# FIXME: this should be the final version %>
-
<% if editable %>
-
<%= turbo_frame_tag turbo_frame_id do %>
-
<%= form_with(**form_attrs, class: "-ml-4") do |form| %>
-
<%= render LooposUi::Inputs::Text.new(
-
model: model,
-
attribute: attribute,
-
readonly: !editable
-
)%>
-
<% end %>
-
<% end %>
-
<% else %>
-
<div class="lui-show-header__title">
-
<%= model.send(attribute) %>
-
</div>
-
<% end %>
-
<%
-
=end %>
-
-
-
then: 0
<% if editable %>
-
<%= turbo_frame_tag turbo_frame_id do %>
-
<%= form_with(**form_attrs) do |form| %>
-
<div class="flex flex-start gap-2" data-controller="inline-edit" data-inline-edit-open-actions-value="<%= open_actions %>">
-
<div class="lui-show-header__title flex max-h-[36px] items-center">
-
<%= form.hidden_field attribute, value: value, data: { "inline-edit-target": "input" } %>
-
<%= hidden_field_tag :inline_edit, true %>
-
<%= content_tag(:span, value,
-
class: "lui-input-text",
-
contenteditable: true,
-
data: { action: "click->inline-edit#openActions input->inline-edit#onInput", "inline-edit-target": "fakeInput" })%>
-
</div>
-
<span class="hidden items-center gap-1 h-fit" data-inline-edit-target="actions">
-
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-xmark",
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
# href: "#", #admin_v2_catalog_product_path(@product),
-
tag_options: {
-
data: {
-
"inline-edit-target": "cancel",
-
action: "click->inline-edit#closeActions click->inline-edit#restore" },
-
type: :button
-
}) %>
-
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-check",
-
kind: :success,
-
type: :secondary,
-
size: :tiny,
-
tag_options: { type: "submit", data: { "inline-edit-target": "submit" } }) %>
-
</span>
-
</div>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<div class="lui-show-header__title">
-
<%= model.send(attribute) %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
class Input < LoopComponent
-
1
include Inputs::BaseInputConcern
-
-
1
option :type, default: -> { "text" }
-
1
option :input_attributes, default: -> { {} }
-
-
1
renders_one :left_addon
-
1
renders_one :right_addon
-
-
1
def initialize(**kwargs)
-
20
then: 2
if kwargs[:model].present? && kwargs[:attribute].present?
-
2
kwargs[:name] = model_name(kwargs[:model], kwargs[:attribute])
-
2
kwargs[:value] = kwargs[:model].send(kwargs[:attribute])
-
then: 0
else: 2
kwargs[:error] =
-
2
else: 18
kwargs[:model].errors[kwargs[:attribute]].first if kwargs[:model].errors[kwargs[:attribute]].present?
-
18
then: 0
else: 18
elsif kwargs[:name].blank?
-
raise ArgumentError, "You must provide a name attribute, or a model"
-
end
-
-
20
super(**kwargs)
-
end
-
-
1
def classes
-
18
then: 0
else: 18
"lui-input #{error.present? ? "lui-input--with-error" : ""}"
-
end
-
-
1
private
-
-
1
def model_name(model, attribute)
-
2
"#{model.model_name.param_key}[#{attribute}]"
-
end
-
-
1
def merged_input_attributes
-
{
-
18
name: name,
-
type: type,
-
value: value,
-
placeholder: placeholder,
-
class: "lui-input__input",
-
mode: mode,
-
form: form,
-
contentEditable: true,
-
data: {
-
"input-target": "input",
-
action: "input->input#onChange change->input#onChange",
-
},
-
}.deep_merge(input_attributes).compact
-
end
-
end
-
end
-
<%#
-
TODO: refactor css classes
-
we have
-
- lui-inner-input (top level), controller attached
-
- lui-input (input wrapper), with addons
-
- lui-input__input (actual input tag)
-
-
Carefull: other wrapper components have css selectors that overide these styles, just changing it will break other
-
components, as well as it's controllers.
-
%>
-
20
then: 18
<% if !readonly %>
-
18
<div data-controller="input" data-input-open-actions-value="<%= open_actions %>" class="lui-inner-input relative flex gap-2"
-
18
data-input-original-input-value="<%= value %>"
-
18
data-input-mode-value="<%= mode %>"
-
18
data-input-form-value="<%= form %>">
-
<div class="w-full flex flex-col">
-
18
then: 0
<% if content.present? %>
-
<%= content %>
-
else: 18
<% else %>
-
36
<%= content_tag(:span, class: "#{classes}") do %>
-
18
then: 4
else: 14
<% if left_addon %>
-
4
<span class="lui-input__addon-left">
-
4
<%= left_addon %>
-
</span>
-
<% end %>
-
18
<%= tag.input(**merged_input_attributes) %>
-
18
then: 2
else: 16
<% if right_addon %>
-
2
<span class="lui-input__addon-right">
-
2
<%= right_addon %>
-
</span>
-
<% end %>
-
18
<span class="lui-input__spinner">
-
18
<%= tag.i(class: "fa-regular fa-spinner") %>
-
</span>
-
<% end %>
-
<% end %>
-
18
then: 0
else: 18
<%= content_tag(:span, help, class: "lui-input__help") if !error.present? && help.present? %>
-
18
then: 0
else: 18
<%= content_tag(:span, error, class: "lui-input__error") if error.present? %>
-
</div>
-
<span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions">
-
18
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-xmark",
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
# href: "#", #admin_v2_catalog_product_path(@product),
-
tag_options: {
-
data: {
-
"input-target": "cancel",
-
action: "click->input#handleClose" },
-
type: :button,
-
disabled: true
-
18
}) %>
-
18
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-check",
-
kind: :success,
-
type: :secondary,
-
size: :tiny,
-
tag_options: {
-
form: form,
-
type: "submit",
-
data: {
-
"input-target": "submit",
-
"action": "click->input#setLoading"
-
},
-
disabled: true
-
18
}) %>
-
</span>
-
else: 2
</div>
-
2
<% elsif custom_readonly %>
-
then: 0
<%# Support for custom readonly inputs, like RichText, it will receive all the props %>
-
<%= content %>
-
else: 2
<% else %>
-
2
<%= content_tag(:span, value.presence || "-", class: "lui-input__value") %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class Ai < LoopComponent
-
1
option :placeholder, Types::Coercible::String, optional: true
-
1
option :spinner, Types::Bool, default: -> { false }
-
1
option :max_rows,
-
Types::Coercible::Integer.constructor { |value|
-
then: 0
else: 0
value.to_i <= 1 ? 1 : value.to_i + 1
-
},
-
default: -> { 7 }
-
end
-
end
-
end
-
<div
-
class="lui-ai-input__wrapper lui-ai-animated-border"
-
data-controller="input-ai"
-
data-input-ai-spinner-value="<%= spinner %>"
-
data-input-ai-max-rows-value="<%= max_rows %>"
-
>
-
<textarea
-
data-input-ai-target="input"
-
rows="1"
-
placeholder="<%= placeholder || t(".placeholder") %>"
-
class="lui-ai-input__input lui-ai-input__textarea"
-
style="--lui-ai-max-rows: <%= max_rows %>;"
-
></textarea>
-
<div data-input-ai-target="buttonWrapper" class="lui-ai-input__button-wrapper lui-ai-input__button-wrapper--center">
-
<%= render LooposUi::Button.new(
-
leading_icon: "fa-solid fa-paper-plane-top",
-
kind: :neutral,
-
type: :secondary,
-
size: :default,
-
tag_options: {
-
class: "lui-ai-input__button",
-
data: {
-
"input-ai-target": "button",
-
action: "click->input-ai#handleButtonClick"
-
},
-
type: :button
-
}
-
) %>
-
then: 0
else: 0
<% if spinner %>
-
<i
-
data-input-ai-target="spinner"
-
class="fa-solid fa-spinner fa-spin lui-ai-input__spinner"
-
></i>
-
<% end %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
module BaseInputConcern
-
1
extend ActiveSupport::Concern
-
-
1
class FormTagHelper
-
1
include ActionView::Helpers::FormTagHelper
-
end
-
-
1
def form_tag_helper
-
@form_tag_helper ||= FormTagHelper.new
-
end
-
-
# TODO: All inputs should use this
-
# If you saw this, please refactor the input to use this
-
1
def field_name(model, attribute, multiple: false)
-
form_tag_helper.field_name(model.model_name.param_key, attribute, multiple: multiple)
-
end
-
-
1
included do
-
# Model usage
-
14
option :model, optional: true
-
14
option :attribute, optional: true
-
-
# Manual usage
-
14
option :name, optional: true
-
14
option :value, Types::Coercible::String, optional: true
-
-
14
option :placeholder, optional: true
-
14
option :error, optional: true
-
-
14
option :help, optional: true
-
-
14
option :mode, default: -> { :inline }, type: Types::Coercible::Symbol.enum(
-
:inline, :form, :autosubmit
-
)
-
-
14
option :form, optional: true
-
-
28
option :readonly, default: -> { false }, type: Types::Bool
-
54
option :custom_readonly, default: -> { false }, type: Types::Bool # This will make the input render the content instead of the value
-
24
option :open_actions, default: -> { false }
-
-
54
option :extra_input_attributes, default: -> { {} }, type: Types::Hash
-
end
-
-
1
def unique_id
-
4
@unique_id ||= [
-
self.class.name.downcase.parameterize,
-
2
then: 0
else: 2
then: 2
else: 0
then: 2
else: 0
model.present? ? dom_id(model) : name&.to_s&.gsub(/\[|\]/, "_"),
-
attribute.presence,
-
rand(10**10),
-
].compact.join("_")
-
end
-
-
1
def base_input_attributes
-
24
[
-
:model,
-
:attribute,
-
:name,
-
:value,
-
:placeholder,
-
:error,
-
:help,
-
:mode,
-
:form,
-
:readonly,
-
:open_actions,
-
].to_h do |o|
-
264
[o, send(o)]
-
end
-
end
-
-
1
[:inline, :form, :autosubmit].each do |mode|
-
3
define_method("#{mode}?") do
-
self.mode == mode
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Inputs
-
1
class Checkbox < LoopComponent
-
1
option :label, optional: true
-
1
option :state, optional: true
-
1
option :value, optional: true
-
1
option :name, optional: true
-
1
option :required, default: -> { false }, type: Types::Bool
-
1
option :readonly, default: -> { false }, type: Types::Bool
-
1
option :form, optional: true
-
1
option :data, optional: true
-
-
1
def checked?
-
then: 0
else: 0
return state == :checked if state.present?
-
-
then: 0
else: 0
val = (value == "-" ? false : value)
-
ActiveModel::Type::Boolean.new.cast(val)
-
end
-
-
1
def indeterminate?
-
state == :indeterminate
-
end
-
-
1
def state_css_class
-
then: 0
else: 0
return "lui-checkbox--#{state}" if state.present?
-
-
then: 0
else: 0
checked? ? "lui-checkbox--checked" : nil
-
end
-
end
-
end
-
end
-
<%= tag.div(
-
class: ["lui-checkbox", state_css_class].compact.join(" "),
-
data: { controller: "checkbox" }
-
) do %>
-
<label class="lui-checkbox__wrapper">
-
<span class="lui-checkbox__input">
-
<%= tag.input(
-
type: "checkbox",
-
data: {
-
checkbox_target: "input",
-
action: "change->checkbox#toggle"
-
}.merge(data || {}),
-
checked: checked?,
-
indeterminate: indeterminate?,
-
class: "lui-checkbox__original",
-
value: value,
-
name: name,
-
required: required,
-
disabled: readonly,
-
form: form
-
) %>
-
<span class="lui-checkbox__inner"></span>
-
</span>
-
then: 0
else: 0
<%= tag.span(class: "lui-checkbox__label") do %>
-
<%= label %><%= content %>
-
<% end if label.present? || content.present? %>
-
</label>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Inputs
-
# Searchable select powered by Tom Select (Rails Blocks–compatible Stimulus controller).
-
# @see https://railsblocks.com/docs/combobox
-
1
class Combobox < LoopComponent
-
1
include BaseInputConcern
-
-
1
option :options, type: [], default: -> { [] } do
-
1
option :value, Types::Coercible::String
-
1
option :text, Types::Coercible::String
-
1
option :disabled, Types::Bool, optional: true, default: -> { false }
-
1
option :data, Types::Hash, optional: true, default: -> { {} }
-
end
-
-
1
option :include_blank, Types::Bool | Types::String, optional: true, default: -> { false }
-
1
option :multiple, Types::Bool, default: -> { false }
-
1
option :disabled, Types::Bool, default: -> { false }
-
1
option :required, Types::Bool, default: -> { false }
-
1
option :label, optional: true
-
1
option :description, optional: true
-
1
option :html_id, optional: true
-
1
option :selected, optional: true
-
-
1
option :url, optional: true
-
1
option :value_field, default: -> { "value" }
-
1
option :label_field, default: -> { "label" }
-
1
option :search_param, default: -> { "query" }
-
1
option :per_page, type: Types::Coercible::Integer, default: -> { 60 }
-
1
option :virtual_scroll, Types::Bool, default: -> { false }
-
1
option :response_data_field, default: -> { "data" }
-
1
option :optgroup_columns, Types::Bool, default: -> { false }
-
-
1
option :searchable, Types::Bool, default: -> { true }
-
1
option :clear_button, Types::Bool, default: -> { true }
-
1
option :open_on_mouse_down, Types::Bool, default: -> { true }
-
1
option :lock_scroll, Types::Bool, default: -> { false }
-
1
option :allow_create, Types::Bool, default: -> { false }
-
1
option :submit_on_change, Types::Bool, default: -> { false }
-
1
option :disable_typing, Types::Bool, default: -> { false }
-
1
option :scroll_buttons, Types::Bool, default: -> { false }
-
1
option :update_field, Types::Bool, default: -> { false }
-
1
option :update_field_target, optional: true
-
1
option :update_field_source, default: -> { "name" }
-
1
option :show_count, Types::Bool, default: -> { false }
-
1
option :count_text, default: -> { "selected" }
-
1
option :count_text_singular, optional: true
-
1
option :tags_position, default: -> { "inline" }
-
1
option :enable_flag_toggle, Types::Bool, default: -> { false }
-
1
option :image_field, optional: true
-
1
option :subtitle_field, optional: true
-
1
option :meta_fields, optional: true
-
1
option :badge_field, optional: true
-
1
option :render_template, optional: true
-
1
option :dropdown_placeholder, default: -> { "Search..." }
-
1
option :no_more_results_text, optional: true
-
1
option :no_search_results_text, optional: true
-
1
option :loading_text, optional: true
-
1
option :create_text, optional: true
-
-
1
option :extra_data, Types::Hash, default: -> { {} }
-
-
1
def initialize(**kwargs)
-
multiple = kwargs.fetch(:multiple, false)
-
helper = LooposUi::Inputs::BaseInputConcern::FormTagHelper.new
-
prime_name_and_model_fields!(kwargs, multiple, helper)
-
normalize_multiple!(kwargs, multiple)
-
super(**kwargs)
-
end
-
-
1
def combobox_field_id
-
html_id.presence || unique_id
-
end
-
-
1
def description_dom_id
-
"#{combobox_field_id}_description"
-
end
-
-
1
def pairs_for_options_helper
-
rows = []
-
then: 0
else: 0
if include_blank
-
then: 0
else: 0
label = include_blank.is_a?(String) ? include_blank : ""
-
rows << [label, ""]
-
end
-
options.each do |o|
-
attrs = {}
-
then: 0
else: 0
attrs[:disabled] = true if o.disabled
-
then: 0
else: 0
attrs[:data] = o.data if o.data.present?
-
then: 0
else: 0
rows << (attrs.empty? ? [o.text, o.value] : [o.text, o.value, attrs])
-
end
-
rows
-
end
-
-
1
def selected_for_options_helper
-
then: 0
if multiple
-
selected_values
-
else: 0
else
-
(selected.presence || value).presence
-
end
-
end
-
-
1
def selected_values
-
Array.wrap(selected).map(&:to_s).reject(&:blank?)
-
end
-
-
1
def readonly_display_text
-
then: 0
if multiple
-
texts = options.select { |o| selected_values.include?(o.value.to_s) }.map(&:text)
-
then: 0
else: 0
texts.presence&.join(", ") || "-"
-
else: 0
else
-
current = (selected.presence || value).to_s
-
then: 0
else: 0
options.find { |o| o.value.to_s == current }&.text.presence || "-"
-
end
-
end
-
-
1
def inline_side_actions?
-
inline? && !multiple && !readonly
-
end
-
-
1
def inline_original_input_value
-
(selected.presence || value).to_s
-
end
-
-
1
def combobox_data_attributes
-
base = {
-
controller: "combobox",
-
combobox_submit_on_change_value: effective_submit_on_change,
-
combobox_dropdown_input_value: searchable,
-
combobox_dropdown_input_placeholder_value: dropdown_placeholder,
-
combobox_clear_button_value: effective_clear_button,
-
combobox_open_on_mouse_down_value: open_on_mouse_down,
-
combobox_lock_scroll_value: lock_scroll,
-
combobox_disable_typing_value: disable_typing,
-
combobox_allow_new_value: allow_create,
-
combobox_scroll_buttons_value: scroll_buttons,
-
combobox_update_field_value: update_field,
-
combobox_update_field_source_value: update_field_source,
-
combobox_per_page_value: per_page,
-
combobox_virtual_scroll_value: virtual_scroll,
-
combobox_optgroup_columns_value: optgroup_columns,
-
combobox_response_data_field_value: response_data_field,
-
combobox_search_param_value: search_param,
-
combobox_show_count_value: show_count,
-
combobox_count_text_value: count_text,
-
combobox_tags_position_value: tags_position,
-
combobox_enable_flag_toggle_value: enable_flag_toggle,
-
combobox_value_field_value: value_field,
-
combobox_label_field_value: label_field,
-
}
-
then: 0
else: 0
base[:combobox_url_value] = url if url.present?
-
then: 0
else: 0
base[:combobox_update_field_target_value] = update_field_target if update_field_target.present?
-
then: 0
else: 0
base[:combobox_count_text_singular_value] = count_text_singular if count_text_singular.present?
-
then: 0
else: 0
base[:combobox_image_field_value] = image_field if image_field.present?
-
then: 0
else: 0
base[:combobox_subtitle_field_value] = subtitle_field if subtitle_field.present?
-
then: 0
else: 0
base[:combobox_meta_fields_value] = meta_fields if meta_fields.present?
-
then: 0
else: 0
base[:combobox_badge_field_value] = badge_field if badge_field.present?
-
then: 0
else: 0
base[:combobox_render_template_value] = render_template if render_template.present?
-
then: 0
else: 0
base[:combobox_no_more_results_text_value] = no_more_results_text if no_more_results_text.present?
-
then: 0
else: 0
base[:combobox_no_search_results_text_value] = no_search_results_text if no_search_results_text.present?
-
then: 0
else: 0
base[:combobox_loading_text_value] = loading_text if loading_text.present?
-
then: 0
else: 0
base[:combobox_create_text_value] = create_text if create_text.present?
-
then: 0
else: 0
base[:combobox_inline_actions_value] = true if inline_side_actions?
-
base.merge(extra_data.symbolize_keys)
-
end
-
-
1
def merged_select_attributes
-
extra = extra_input_attributes.deep_dup
-
extra_class = extra.delete(:class)
-
extra_data = (extra.delete(:data) || {}).symbolize_keys
-
merged_data = combobox_data_attributes.merge(extra_data)
-
then: 0
else: 0
if inline_side_actions?
-
merged_data = merged_data.merge(
-
action: "change->input#onChange",
-
input_target: "input",
-
)
-
end
-
{
-
id: combobox_field_id,
-
class: class_names("lui-combobox__select", "w-full", extra_class),
-
multiple: multiple,
-
disabled: disabled,
-
required: required,
-
form: form,
-
style: "visibility: hidden;",
-
data: merged_data,
-
aria: aria_attributes,
-
}.deep_merge(extra.compact)
-
end
-
-
1
def aria_attributes
-
then: 0
else: 0
return {} if description.blank?
-
-
{ describedby: description_dom_id }
-
end
-
-
1
def wrapper_classes
-
class_names(
-
"lui-combobox w-full flex flex-col gap-1",
-
combobox_mode_modifier_class,
-
then: 0
else: 0
error.present? ? "lui-combobox--error" : nil,
-
*Array.wrap(@additional_classes).compact,
-
)
-
end
-
-
1
def effective_submit_on_change
-
submit_on_change || autosubmit?
-
end
-
-
1
def effective_clear_button
-
then: 0
else: 0
return false if form? || autosubmit?
-
then: 0
else: 0
return false if required
-
-
clear_button
-
end
-
-
1
def combobox_mode_modifier_class
-
then: 0
if autosubmit?
-
else: 0
"lui-combobox--autosubmit"
-
then: 0
elsif form?
-
"lui-combobox--form"
-
else: 0
else
-
"lui-combobox--inline"
-
end
-
end
-
-
1
private
-
-
1
def prime_name_and_model_fields!(kwargs, multiple, helper)
-
model = kwargs[:model]
-
attribute = kwargs[:attribute]
-
then: 0
if model.present? && attribute.present?
-
else: 0
prime_from_model!(kwargs, model, attribute, multiple, helper)
-
then: 0
else: 0
elsif kwargs[:name].blank?
-
raise ArgumentError, "You must provide a name attribute, or a model and attribute"
-
end
-
end
-
-
1
def prime_from_model!(kwargs, model, attribute, multiple, helper)
-
kwargs[:name] ||= helper.field_name(model.model_name.param_key, attribute, multiple: multiple)
-
apply_raw_attribute_to_selection!(kwargs, model, attribute, multiple)
-
copy_model_error!(kwargs, model, attribute)
-
end
-
-
1
def apply_raw_attribute_to_selection!(kwargs, model, attribute, multiple)
-
then: 0
else: 0
return if kwargs.key?(:selected) || kwargs.key?(:value)
-
-
raw = model.public_send(attribute)
-
assign_raw_to_selection!(kwargs, multiple, raw)
-
end
-
-
1
def assign_raw_to_selection!(kwargs, multiple, raw)
-
then: 0
if multiple
-
kwargs[:selected] = Array.wrap(raw).map(&:to_s).reject(&:blank?)
-
else: 0
else
-
then: 0
else: 0
kwargs[:value] = raw&.to_s
-
end
-
end
-
-
1
def copy_model_error!(kwargs, model, attribute)
-
errs = model.errors[attribute]
-
then: 0
else: 0
then: 0
else: 0
kwargs[:error] ||= errs.first&.to_s if errs.present?
-
end
-
-
1
def normalize_multiple!(kwargs, multiple)
-
else: 0
then: 0
return unless multiple
-
-
ensure_multiple_name!(kwargs)
-
normalize_selected_vs_value!(kwargs)
-
end
-
-
1
def ensure_multiple_name!(kwargs)
-
n = kwargs[:name].to_s
-
else: 0
then: 0
kwargs[:name] = "#{n}[]" unless n.end_with?("[]")
-
end
-
-
1
def normalize_selected_vs_value!(kwargs)
-
then: 0
if kwargs[:selected].nil?
-
normalize_nil_selected!(kwargs)
-
else: 0
else
-
kwargs[:selected] = Array.wrap(kwargs[:selected]).map(&:to_s).reject(&:blank?)
-
kwargs[:value] = nil
-
end
-
end
-
-
1
def normalize_nil_selected!(kwargs)
-
then: 0
if kwargs[:value].is_a?(Array)
-
kwargs[:selected] = kwargs[:value].map(&:to_s).reject(&:blank?)
-
else: 0
kwargs[:value] = nil
-
then: 0
elsif kwargs[:multiple] && !kwargs[:value].is_a?(Array)
-
kwargs[:selected] = Array.wrap(kwargs[:value]).map(&:to_s).reject(&:blank?)
-
kwargs[:value] = nil
-
else: 0
else
-
kwargs[:selected] = []
-
end
-
end
-
end
-
end
-
end
-
then: 0
<% if readonly %>
-
<%= content_tag(:span, readonly_display_text, class: "lui-input__value") %>
-
else: 0
<% else %>
-
<div class="<%= wrapper_classes %>">
-
then: 0
else: 0
<% if label.present? %>
-
<%= label_tag combobox_field_id, class: "text-sm font-medium text-content" do %>
-
<%= label %>
-
then: 0
else: 0
<% if required %>
-
<span class="text-semantic-error-default" aria-hidden="true">*</span>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if description.present? %>
-
<p id="<%= description_dom_id %>" class="copy-12 text-foreground">
-
<%= description %>
-
</p>
-
<% end %>
-
then: 0
<% if inline_side_actions? %>
-
<%= tag.div(
-
class: "lui-inner-input lui-combobox__inline-row relative flex w-full gap-2 items-center",
-
data: {
-
controller: "input",
-
input_open_actions_value: open_actions,
-
input_original_input_value: inline_original_input_value,
-
input_mode_value: "inline",
-
input_form_value: form,
-
},
-
) do %>
-
<div class="min-w-0 w-full">
-
<%= select_tag(
-
name,
-
options_for_select(pairs_for_options_helper, selected_for_options_helper),
-
merged_select_attributes,
-
) %>
-
</div>
-
<span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit shrink-0" data-input-target="actions">
-
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-xmark",
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
tag_options: {
-
data: {
-
"input-target": "cancel",
-
action: "click->input#handleClose",
-
},
-
type: :button,
-
disabled: true,
-
},
-
) %>
-
<%= render LooposUi::Button.new(
-
leading_icon: "fa-regular fa-check",
-
kind: :success,
-
type: :secondary,
-
size: :tiny,
-
tag_options: {
-
form: form,
-
type: "submit",
-
data: {
-
"input-target": "submit",
-
action: "click->input#setLoading",
-
},
-
disabled: true,
-
},
-
) %>
-
</span>
-
<% end %>
-
else: 0
<% else %>
-
<%= select_tag(
-
name,
-
options_for_select(pairs_for_options_helper, selected_for_options_helper),
-
merged_select_attributes,
-
) %>
-
<% end %>
-
then: 0
else: 0
<% if help.present? && error.blank? %>
-
<%= content_tag(:span, help, class: "lui-input__help") %>
-
<% end %>
-
then: 0
else: 0
<% if error.present? %>
-
<%= content_tag(:span, error, class: "lui-input__error") %>
-
<% end %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class Date < LoopComponent
-
1
include BaseInputConcern
-
-
1
option :is_range, default: -> { false }
-
1
option :kind, default: -> { "DatePicker" }
-
1
option :exclude, default: -> { false }
-
1
option :limit_min, default: -> { nil }
-
1
option :lang, default: -> { "pt" }
-
1
option :variant, default: -> { "core" }
-
-
GRANULARITY = {
-
1
DatePicker: "date",
-
DateTimePicker: "date_time",
-
YearPicker: "year",
-
MonthYearPicker: "month_year",
-
TimePicker: "time",
-
}
-
-
1
def classes
-
then: 0
else: 0
"lui-input lui-input--#{error.present? ? "with-error" : ""}"
-
end
-
-
1
def input_attributes
-
base_input_attributes.merge({
-
input_attributes: {
-
is_range: is_range,
-
kind: kind,
-
exclude: exclude,
-
limit_min: limit_min,
-
lang: lang,
-
variant: variant,
-
data: {
-
"date-input-target": "input",
-
},
-
},
-
})
-
end
-
end
-
end
-
end
-
<div
-
id="<%= unique_id %>"
-
data-controller="date-input"
-
data-date-input-name-value="<%= name %>"
-
data-date-input-mode-value="<%= mode %>"
-
data-date-input-input-outlet="<%="##{unique_id} .lui-inner-input" %>"
-
class="lui-date lui-date--<%= mode %> relative"
-
>
-
<%= render LooposUi::Input.new(**input_attributes) do %>
-
then: 0
else: 0
<% if !readonly %>
-
<%= react_component("DatetimePicker", {
-
name: name,
-
selectedDate: value,
-
kind: kind,
-
isRange: is_range,
-
placeholder: placeholder,
-
inputClass: "#{classes}",
-
variant: variant,
-
granularity: GRANULARITY[kind.to_sym],
-
lang: lang
-
}) %>
-
<% end %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class File < LoopComponent
-
1
module FileInputConcern
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
4
option :name, optional: true
-
4
option :mode, Types::Symbol.enum(:inline, :form, :autosubmit), default: -> { :inline }
-
4
option :model, Types::Instance(ActiveRecord::Base), optional: true
-
4
option :attribute, Types::Coercible::Symbol, optional: true
-
4
option :multiple, default: -> { false }, type: Types::Bool
-
4
option :delete_path, Types::Coercible::String, optional: true
-
4
option :accept,
-
Types::Array.of(Types::Coercible::String) | Types::Symbol.enum(:all),
-
optional: true,
-
default: -> { LooposUi.config.file_default_accept_formats }
-
-
4
option :form_id, Types::Coercible::String, optional: true
-
# Update context can be used to render the component differently when streaming an update, for example
-
4
option :context, Types::Symbol.enum(:default, :action), default: -> { :default }
-
end
-
end
-
1
include FileInputConcern
-
-
1
option :value, Types::Coercible::String, optional: true
-
-
1
option :readonly, default: -> { false }, type: Types::Bool
-
-
1
def initialize(*params, **data)
-
super
-
validate_input_identity!
-
validate_delete_path_requirement!
-
end
-
-
1
def call
-
mimefy_accept_types
-
-
then: 0
if mode == :form
-
else: 0
render(LooposUi::Inputs::File::FormFile.new(**args))
-
then: 0
elsif multiple?
-
render(LooposUi::Inputs::File::Multiple.new(**args))
-
else: 0
else
-
render(LooposUi::Inputs::File::Single.new(**args.except(:context)))
-
end
-
end
-
-
1
private
-
-
1
def validate_input_identity!
-
then: 0
else: 0
return if mode == :form
-
then: 0
else: 0
return if attribute.present? || name.present?
-
-
raise ArgumentError, "attribute or name are required when mode is not :form"
-
end
-
-
1
def validate_delete_path_requirement!
-
then: 0
else: 0
return if mode == :form || readonly
-
else: 0
then: 0
return unless model.present? && attribute.present?
-
then: 0
else: 0
return if delete_path.present?
-
-
raise ArgumentError, "delete_path is required for editable model-backed file inputs"
-
end
-
-
1
def multiple?
-
then: 0
else: 0
return multiple if name.present?
-
-
model.class.reflect_on_attachment(attribute).macro == :has_many_attached
-
end
-
-
1
def mimefy_accept_types
-
then: 0
else: 0
if accept == :all
-
@accept = nil
-
-
return
-
end
-
-
@accept = accept.flat_map do |mime|
-
::MIME::Types.type_for(".#{mime}") || ::MIME::Types[mime.to_s]
-
end.compact_blank!
-
end
-
-
1
def args
-
self.class.dry_initializer.definitions.keys.filter_map do |key|
-
val = send(key)
-
then: 0
else: 0
next [key, val] if val.present?
-
then: 0
else: 0
next [key, val] if key == :value && !val.nil?
-
then: 0
else: 0
next [key, val] if key == :readonly && val
-
-
nil
-
end.to_h
-
end
-
end
-
end
-
end
-
<%= tag.div(class: "lui-input-file__actions lui-action-menu__option") do %>
-
<%= render LooposUi::Button.new(
-
icon: :file_arrow_down,
-
size: :small,
-
type: :tertiary,
-
kind: :neutral,
-
tooltip_text: t('.download_file'),
-
href: attachment.url,
-
tag_options: {
-
download: true,
-
data: { turbo: false }
-
})%>
-
<%= render LooposUi::Modal.new(title: t('.delete_file')) do |modal| %>
-
<% modal.with_trigger_button(
-
icon: :trash,
-
size: :small,
-
type: :tertiary,
-
kind: :neutral,
-
tooltip_text: t('.delete_file'))%>
-
<% modal.with_custom_content do %>
-
<p><%= t('.delete_file_warning') %></p>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t('.delete'),
-
kind: :danger,
-
tag_options: {
-
type: :button,
-
data: delete_data
-
})%>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class File
-
1
class FileActions < LoopComponent
-
1
param :attachment, Types.Instance(ActiveStorage::Attachment) | Types.Instance(ActiveStorage::Attached::One)
-
1
option :delete_data, Types::Coercible::Hash, default: -> { {} }
-
1
then: 0
else: 0
delegate :t, to: LooposUi::Inputs::File
-
end
-
end
-
end
-
end
-
<% file_attributes = form_file_input_attributes %>
-
then: 0
<% if readonly %>
-
<div class="lui-input-file-form lui-input-file-form--readonly" id="div_<%= form_file_input_id %>">
-
<span
-
class="lui-input-file-form__file-name"
-
id="span_<%= form_file_input_id %>"
-
>
-
<%= initial_form_file_display_name %>
-
</span>
-
</div>
-
else: 0
<% else %>
-
<div
-
class="lui-input-file-form"
-
data-controller="form-file"
-
data-form-file-form-id-value="<%= form_id.to_s %>"
-
id="div_<%= file_attributes[:id] %>"
-
>
-
<%= tag.input(
-
**file_attributes.merge(
-
class: "lui-input-file-form__input sr-only"
-
)
-
) %>
-
-
<%= render LooposUi::Button.new(
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
text: t('.select_file'),
-
icon: "fa-regular fa-file-arrow-up",
-
tag: :label,
-
tag_options: {
-
for: file_attributes[:id],
-
id: "#{file_attributes[:id]}_label",
-
data: {
-
form_file_target: "button",
-
select_text: t('.select_file'),
-
replace_text: t('.replace_file')
-
}
-
}
-
) %>
-
-
<span
-
data-form-file-target="fileName"
-
class="lui-input-file-form__file-name"
-
id="span_<%= file_attributes[:id] %>"
-
>
-
<%= initial_form_file_display_name %>
-
</span>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class File
-
1
class FormFile < LoopComponent
-
1
include BaseInputConcern
-
1
include FileInputConcern
-
-
1
then: 0
else: 0
delegate :t, to: LooposUi::Inputs::File
-
-
1
def file
-
then: 0
else: 0
model&.send(attribute)
-
end
-
-
1
def form_file_input_id
-
"#{unique_id}-file-input"
-
end
-
-
1
def input_name
-
then: 0
if model.present? && attribute.present?
-
else: 0
field_name(model, attribute, multiple: false)
-
then: 0
elsif name.present?
-
name
-
else: 0
else
-
raise ArgumentError, "You must provide either model and attribute, or a name"
-
end
-
end
-
-
1
def form_file_input_attributes
-
{
-
type: "file",
-
id: form_file_input_id,
-
name: input_name,
-
"data-form-file-target": "input",
-
"data-action": "change->form-file#updateFileName",
-
class: "lui-input-file-form__input",
-
form: form_id,
-
then: 0
else: 0
accept: if accept.present?
-
then: 0
else: 0
accept.is_a?(Array) ? accept.join(",") : accept
-
end,
-
}.compact
-
end
-
-
# Initial label in the UI (file inputs cannot set a value).
-
1
def initial_form_file_display_name
-
then: 0
else: 0
return t(".no_file_chosen") if value.blank?
-
-
path = value.to_s.split(/[?#]/, 2).first.to_s
-
::File.basename(path)
-
end
-
end
-
end
-
end
-
end
-
then: 0
<% if readonly %>
-
<%= tag.div(
-
class: "lui-input-file-container lui-input-file-container--readonly",
-
id: unique_id,
-
) do %>
-
<div class="lui-input-file lui-input-file--readonly">
-
<div class="flex justify-end items-center gap-[2px] min-w-0">
-
<%= tag.span(class: "lui-input-file__text") do %>
-
then: 0
<% if has_uploaded_files? %>
-
<%= t('.files_uploaded', file_count: uploaded_files_count) %>
-
else: 0
<% else %>
-
<%= t('.no_file_chosen') %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(
-
class: "lui-input-file-container",
-
id: unique_id,
-
data: {
-
controller: "input-file",
-
"input-file-form-value": form_id,
-
"input-file-delete-path-value": delete_path,
-
action: "click->input-file#toggleActions"
-
}) do %>
-
<div class="lui-input-file">
-
<%= render LooposUi::Button.new(
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
text: t('.select_files'),
-
icon: "fa-regular fa-file-arrow-up",
-
tag: :label,
-
tag_options: { for: "#{unique_id}-file-input" }
-
) %>
-
-
<%= tag.input(**file_input_attributes) %>
-
-
<div class="flex justify-end items-center gap-[2px] min-w-0">
-
<%= tag.span(class: "lui-input-file__text") do %>
-
then: 0
<% if has_uploaded_files? %>
-
<%= t('.files_uploaded', file_count: uploaded_files_count) %>
-
else: 0
<% else %>
-
<%= t('.no_file_chosen') %>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<%= tag.div(class: "lui-input-file__actions") do %>
-
<%= render LooposUi::Popover.new(
-
open: context == :action,
-
position: :bottom_right,
-
anchor: :top_right,
-
anchor_selector: "##{unique_id}",
-
rotate_toggle: true,
-
system_arguments: {
-
data: {
-
"input-file-target": "popover"
-
}
-
}
-
) do |popover| %>
-
<% popover.with_custom_toggle do %>
-
<i class="fa-regular fa-chevron-down text-[10px]"></i>
-
<% end %>
-
<% popover.with_target do %>
-
<div class="lui-input-files lui-action-menu">
-
<% files.each do |file| %>
-
<%= render LooposUi::Inputs::File::Option.new(file, delete_data: {
-
action: "click->input-file#destroy",
-
"input-file-attachment-signed-id-param": file.signed_id,
-
}) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end if can_manage_uploaded_files? %>
-
<span class="lui-input-file__spinner">
-
<%= tag.i(class: "fa-regular fa-spinner") %>
-
</span>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class File
-
1
class Multiple < LoopComponent
-
1
include BaseInputConcern
-
1
include FileInputConcern
-
-
1
then: 0
else: 0
delegate :t, to: LooposUi::Inputs::File
-
-
1
def files
-
then: 0
else: 0
model&.send(attribute) || []
-
end
-
-
1
def input_name
-
then: 0
if model.present? && attribute.present?
-
else: 0
field_name(model, attribute, multiple: true)
-
then: 0
elsif name.present?
-
name
-
else: 0
else
-
raise ArgumentError, "You must provide either model and attribute, or a name"
-
end
-
end
-
-
1
def uploaded_files_count
-
then: 0
else: 0
return files.count if files.present?
-
then: 0
else: 0
return 0 if value.blank?
-
then: 0
else: 0
return value.size if value.is_a?(Array)
-
-
1
-
end
-
-
1
def has_uploaded_files?
-
uploaded_files_count.positive?
-
end
-
-
1
def can_manage_uploaded_files?
-
files.present? && delete_path.present?
-
end
-
-
1
def accept_attribute
-
then: 0
else: 0
return if accept.blank?
-
-
then: 0
else: 0
accept.is_a?(Array) ? accept.join(",") : accept
-
end
-
-
1
def file_input_attributes
-
{
-
multiple: true,
-
name: input_name,
-
data: {
-
"input-file-target": "file",
-
"action": "change->input-file#upload",
-
},
-
id: "#{unique_id}-file-input",
-
class: "hidden",
-
type: "file",
-
form: form_id,
-
accept: accept_attribute,
-
}
-
end
-
end
-
end
-
end
-
end
-
<%# FIXME: LOOPOS-32453 using styles from action menu, because it's not yet migrated to the popover API we need here %>
-
<div class="lui-input-files__item lui-action-menu__option">
-
<%= tag.span(attachment.filename, class: "lui-input-files__item-name") %>
-
<div class="lui-input-files__item__icons">
-
<%= render LooposUi::Inputs::File::FileActions.new(attachment, delete_data: delete_data) %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class File
-
1
class Option < LoopComponent
-
1
param :attachment, Types.Instance(ActiveStorage::Attachment)
-
1
option :delete_data, Types::Coercible::Hash, default: -> { {} }
-
end
-
end
-
end
-
end
-
-
then: 0
<% if readonly %>
-
<%= tag.div(
-
class: "lui-input-file-container lui-input-file-container--readonly",
-
id: unique_id,
-
) do %>
-
<div class="lui-input-file lui-input-file--readonly">
-
<div class="flex flex-1 min-w-0 justify-end items-center gap-[2px]">
-
then: 0
else: 0
<%= tag.span(class: ["lui-input-file__text", has_display_file? ? "lui-input-file__text--with_file" : ""]) do %>
-
then: 0
<% if has_display_file? %>
-
<%= display_file_name %>
-
else: 0
<% else %>
-
<%= t('.no_file_chosen') %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(
-
class: "lui-input-file-container",
-
id: unique_id,
-
data: {
-
controller: "input-file",
-
"input-file-form-value": form_id,
-
"input-file-delete-path-value": delete_path }) do %>
-
<div class="lui-input-file">
-
<%= render LooposUi::Button.new(
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
then: 0
else: 0
text: has_display_file? ? t('.replace_file') : t('.select_file'),
-
icon: "fa-regular fa-file-arrow-up",
-
tag: :label,
-
tag_options: {
-
for: "#{unique_id}-file-input",
-
data: {
-
"input-file-target": "button",
-
"select-text": t(".select_file"),
-
"replace-text": t(".replace_file"),
-
},
-
}
-
) %>
-
<%= tag.input(**file_input_attributes) %>
-
<div class="flex flex-1 min-w-0 justify-end items-center gap-[2px]">
-
<%= tag.span(
-
then: 0
else: 0
class: ["lui-input-file__text", has_display_file? ? "lui-input-file__text--with_file" : ""],
-
data: { "input-file-target": "fileName" },
-
) do %>
-
then: 0
<% if has_display_file? %>
-
<%= display_file_name %>
-
else: 0
<% else %>
-
<%= t('.no_file_chosen') %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::Inputs::File::FileActions.new(file,
-
delete_data: {
-
action: "click->input-file#destroy",
-
"input-file-attachment-signed-id-param": file.signed_id,
-
}
-
) if show_delete_actions? %>
-
<span class="lui-input-file__spinner">
-
<%= tag.i(class: "fa-regular fa-spinner") %>
-
</span>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class File
-
1
class Single < LoopComponent
-
1
include BaseInputConcern
-
1
include FileInputConcern
-
-
1
then: 0
else: 0
delegate :t, to: LooposUi::Inputs::File
-
-
1
def file
-
then: 0
else: 0
model&.send(attribute)
-
end
-
-
1
def input_name
-
then: 0
if model.present? && attribute.present?
-
else: 0
field_name(model, attribute, multiple: false)
-
then: 0
elsif name.present?
-
name
-
else: 0
else
-
raise ArgumentError, "You must provide either model and attribute, or a name"
-
end
-
end
-
-
1
def display_file_name
-
then: 0
else: 0
return file.filename.to_s if file.present?
-
then: 0
else: 0
return if value.blank?
-
-
path = value.to_s.split(/[?#]/, 2).first.to_s
-
::File.basename(path)
-
end
-
-
1
def has_display_file?
-
display_file_name.present?
-
end
-
-
1
def show_delete_actions?
-
file.present? && delete_path.present?
-
end
-
-
1
def accept_attribute
-
then: 0
else: 0
return if accept.blank?
-
-
then: 0
else: 0
accept.is_a?(Array) ? accept.join(",") : accept
-
end
-
-
1
def file_input_attributes
-
{
-
name: input_name,
-
data: {
-
"input-file-target": "file",
-
"action": "change->input-file#upload",
-
},
-
id: "#{unique_id}-file-input",
-
class: "hidden",
-
type: "file",
-
form: form_id,
-
accept: accept_attribute,
-
}
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Inputs
-
1
class Money < LoopComponent
-
1
include BaseInputConcern
-
-
1
option :currency, Types::Coercible::String | Types::Instance(::Money::Currency)
-
3
option :min, default: -> { 0 }
-
3
option :max, default: -> { nil }
-
3
option :step, default: -> { 0.01 }
-
3
option :data_attributes, default: -> { {} }
-
-
1
def input_attributes
-
6
base_input_attributes.merge({
-
input_attributes: {
-
min: min,
-
max: max,
-
step: step,
-
inputmode: "decimal",
-
pattern: "[0-9]*[.,]?[0-9]*",
-
data: data_attributes,
-
},
-
type: :number,
-
mode: mode,
-
})
-
end
-
-
1
def currency_symbol
-
2
else: 0
case currency
-
when: 2
when String
-
2
::Money::Currency.new(currency).symbol
-
when: 0
when ::Money::Currency
-
currency.symbol
-
end
-
end
-
end
-
end
-
end
-
2
<div class="lui-money lui-money--<%= mode %> relative" data-controller="money-input">
-
2
<%= render LooposUi::Input.new(**input_attributes.merge(
-
input_attributes: input_attributes[:input_attributes].merge(
-
data: { "money-input-target": "input" }.merge(input_attributes.dig(:input_attributes, :data).presence || {})
-
)
-
2
)) do |input| %>
-
2
<% input.with_left_addon do %>
-
2
<span class="lui-money__currency text-gray-700">
-
2
<%= currency_symbol %>
-
</span>
-
<% end %>
-
<% end %>
-
2
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class Number < LoopComponent
-
1
include BaseInputConcern
-
-
1
option :min, default: -> { nil }
-
1
option :max, default: -> { nil }
-
1
option :step, default: -> { 1 }
-
1
option :with_actions, default: -> { true }
-
-
1
def input_attributes
-
base_input_attributes.merge({
-
input_attributes: {
-
min: min,
-
max: max,
-
step: step,
-
inputmode: "decimal",
-
data: {
-
"number-input-target": "input",
-
},
-
},
-
type: :number,
-
mode: mode,
-
})
-
end
-
end
-
end
-
end
-
<div data-controller="number-input" data-number-input-step-value="<%= step %>" data-number-input-mode-value="<%= mode %>">
-
<div class="lui-number lui-number--<%= mode %> relative">
-
<%= render LooposUi::Input.new(**input_attributes) do |input| %>
-
then: 0
else: 0
<% if with_actions %>
-
<% input.with_right_addon do %>
-
<div class="flex gap-2">
-
<i class="fa-regular fa-minus cursor-pointer" data-action="click->number-input#decrease"></i>
-
<i class="fa-regular fa-plus cursor-pointer" data-action="click->number-input#increase"></i>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class RichText < LoopComponent
-
1
include BaseInputConcern
-
-
1
option :upload_endpoint, Types::Coercible::String, optional: true
-
-
1
def input_attributes
-
base_input_attributes.merge({
-
type: :rich_text,
-
mode: mode,
-
input_attributes: {},
-
custom_readonly: true,
-
})
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Input.new(**input_attributes) do %>
-
<div class="lui-rich_text">
-
<%=
-
react_component(
-
"RichTextEditor",
-
{
-
value: value,
-
placeholder: placeholder,
-
name: name,
-
language: I18n.locale,
-
readOnly: readonly,
-
uploadEndpoint: upload_endpoint,
-
# TODO: Add all the props to the component
-
}
-
)
-
%>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class Search < LoopComponent
-
1
include BaseInputConcern
-
-
3
option :expanded, Types::Bool, default: -> { false } # DEPRECATED, will not be used anymore
-
3
option :data_attributes, default: -> { {} }
-
3
option :clearable, Types::Bool, default: -> { true }
-
-
# Overrides from BaseInputConcern
-
3
option :mode, Types::Coercible::Symbol.enum(:autosubmit), default: -> { :autosubmit }
-
3
option :event_only, Types::Bool, default: -> { true }
-
-
1
def input_attributes
-
2
base_input_attributes.deep_merge({
-
type: "search",
-
input_attributes: {
-
data: {
-
search_target: "input",
-
action: "input->search#toggleClearButton input->input#setEditing input->search#onInput change->search#onInput",
-
}.deep_merge(data_attributes),
-
},
-
})
-
end
-
-
1
def classes
-
2
"lui-search"
-
end
-
end
-
end
-
end
-
2
<div data-controller="search"
-
2
id="<%= unique_id %>"
-
2
class="<%= classes %>"
-
2
data-search-input-outlet="<%="##{unique_id} .lui-inner-input" %>"
-
2
data-search-event-only-value="<%= event_only %>"
-
>
-
4
<%= render LooposUi::Input.new(**input_attributes) do |input| %>
-
2
<% input.with_left_addon do %>
-
2
<div class="text-[12px] flex items-center text-center">
-
<i class="fa-regular fa-magnifying-glass text-gray-400"></i>
-
</div>
-
<% end %>
-
2
then: 2
else: 0
<% if clearable %>
-
2
<% input.with_right_addon do %>
-
2
<span class="flex">
-
<i class="fa-regular fa-xmark cursor-pointer text-gray-400"
-
data-search-target="clearButton"
-
data-action="click->search#clear click->input#finishEditing">
-
</i>
-
</span>
-
<% end %>
-
<% end %>
-
<% end %>
-
2
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class Select < LoopComponent
-
1
include BaseInputConcern
-
1
include LooposUi::FaviconAware
-
-
1
option :options, type: [], default: -> { [] } do
-
1
option :value, Types::Coercible::String
-
1
option :text, Types::Coercible::String
-
1
option :icon, Types::Coercible::String, optional: true
-
1
option :tooltip, Types::Coercible::String, optional: true
-
end
-
-
1
option :placeholder, default: -> { nil }
-
1
option :portal_target, default: -> { nil }
-
1
option :include_blank, Types::Bool | Types::String, default: -> { false }
-
1
option :clearable, Types::Bool, default: -> { true }
-
-
1
mod :blank_option_selected, base: "lui-input-select", condition: -> {
-
then: 0
else: 0
include_blank && selected_option&.value == ""
-
}
-
-
1
def input_attributes
-
base_input_attributes.merge({
-
then: 0
else: 0
value: selected_option&.text,
-
input_attributes: {
-
readonly: true,
-
placeholder: placeholder,
-
then: 0
else: 0
value: selected_option&.text,
-
data: {
-
"input-select-target": "textInput",
-
},
-
}.merge(extra_input_attributes.presence || {}),
-
})
-
end
-
-
1
def selected_option
-
then: 0
if include_blank
-
options.find { |o| o.value == value } || options.first
-
else: 0
else
-
options.find { |o| o.value == value }
-
end
-
end
-
-
1
def options
-
case include_blank
-
when: 0
when String
-
[Struct.new(:value, :text, :icon, :tooltip).new("", include_blank, nil, nil), *@options]
-
when: 0
when true
-
[Struct.new(:value, :text, :icon, :tooltip).new("", "", nil, nil), *@options]
-
else: 0
else
-
@options
-
end
-
end
-
-
1
def blank_option?(option)
-
option.value.blank? && (option.text.blank? || option.text == include_blank)
-
end
-
end
-
end
-
end
-
then: 0
<% if !readonly %>
-
<%= tag.div(
-
id: unique_id,
-
class: "grow",
-
data: {
-
controller: "input-select",
-
input_select_input_outlet: "##{unique_id} .lui-inner-input",
-
input_select_mode_value: mode,
-
input_select_form_value: form,
-
input_select_portal_target_value: portal_target,
-
}) do
-
%>
-
<div class="lui-input-select lui-input-select--<%= mode %> relative <%= classes %>">
-
<%= render LooposUi::Input.new(**input_attributes) do |input| %>
-
<% input.with_right_addon do %>
-
<span class="flex flex-row items-center gap-2">
-
then: 0
else: 0
then: 0
else: 0
<i class="<%= selected_option.blank? ? 'opacity-0' : '' %> fa-solid fa-xmark cursor-pointer text-gray-400 lui-input-select__clear <%= clearable ? '' : 'hidden!' %>"
-
data-input-select-target="clearButton"
-
data-action="click->input-select#clear">
-
</i>
-
<div data-input-select-target="icon">
-
<%# FIXME: Color should not be hardcoded %>
-
<%= render LooposUi::Icon.new(icon: "fa-regular fa-chevron-down", size: 12, color: "#212529") %>
-
</div>
-
</span>
-
<% end %>
-
<% end %>
-
<div data-input-select-target="menu" class="lui-input-select__wrapper">
-
<div class="lui-input-select__options" role="listbox">
-
<% options.each do |option| %>
-
then: 0
else: 0
<div class="lui-input-select__option <%= blank_option?(option) ? 'lui-input-select__option--blank' : '' %>"
-
role="option"
-
data-input-select-target="option"
-
data-text="<%= option.text %>"
-
data-value="<%= option.value %>">
-
<div class="lui-input-select__option-wrapper">
-
<div class="lui-input-select__option-text">
-
then: 0
else: 0
<%= tag.i(class: faviconize(option.icon)) if option.icon %>
-
<span role="text"><%= option.text %></span>
-
then: 0
else: 0
<%= render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", size: "12", text: option.tooltip) if option.tooltip %>
-
</div>
-
then: 0
else: 0
then: 0
else: 0
<%= tag.i(class: "#{faviconize("fa-regular fa-check")} #{selected_option && selected_option.value&.eql?(option.value) ? '' : 'hidden!'}", data: { "input-select-target": "check" }) %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
<%= hidden_field_tag(
-
input_attributes[:name],
-
value,
-
form: form,
-
data: {
-
input_select_target: "hiddenInput"
-
})%>
-
<% end %>
-
<% else %>
-
else: 0
<%
-
selected_option = (options.presence || []).find { |o| o.value.to_s == value.to_s }
-
%>
-
then: 0
else: 0
<%= content_tag(:span, selected_option&.text.presence || "-", class: "lui-input__value") %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class Select2 < LoopComponent
-
1
include BaseInputConcern
-
1
include LooposUi::FaviconAware
-
-
1
class Option < Dry::Struct
-
1
attribute :value, Types::Coercible::String
-
1
attribute :text, Types::Coercible::String
-
1
attribute :icon, Types::Coercible::String.optional.default("".freeze)
-
1
attribute :tooltip, Types::Coercible::String.optional.default("".freeze)
-
1
attribute :child_options, Types::Array.of(Option).optional.default([].freeze)
-
1
attribute :attributes, Types::Hash.optional.default({}.freeze)
-
end
-
-
1
option :options, type: [Option], default: -> { [] }
-
-
1
option :multiple, type: Types::Bool, default: -> { false }
-
1
option :placeholder, default: -> { nil }
-
1
option :include_blank, Types::Bool | Types::String, default: -> { false }
-
1
option :option_indent_size, type: Types::Coercible::Integer, default: -> { 20 }
-
1
option :parent_selects_children, default: -> { true }
-
1
option :show_actions_in_menu, default: -> { false }
-
1
option :portal_target, default: -> { nil }
-
1
option :clearable, Types::Bool, default: -> { true }
-
1
option :searchable, Types::Bool, default: -> { true }
-
1
option :submit_selected_value, Types::Bool, default: -> { false }
-
1
option :bind_hidden_input_to_form, Types::Bool, default: -> { false }
-
1
option :searchable, Types::Bool, default: -> { true }
-
1
option :show_loading_on_submit, Types::Bool, default: -> { true }
-
-
1
def input_attributes
-
base_input_attributes.merge({
-
value: presented_selected_text,
-
input_attributes: {
-
readonly: true,
-
placeholder: placeholder,
-
value: presented_selected_text,
-
data: {
-
"input-select-target": "textInput",
-
"action": "select2:updateController->input-select#updateValues",
-
},
-
},
-
})
-
end
-
-
1
def hidden_input_name
-
attribute_name = input_attributes[:name].presence
-
then: 0
else: 0
if attribute_name.blank? && model.present? && attribute.present?
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
model_name = model.present? ? dom_id(model) : name&.to_s&.gsub(/\[|\]/, "_")
-
"#{model_name}[#{attribute}]"
-
end
-
-
else: 0
then: 0
return attribute_name unless multiple
-
-
attribute_name + "[]"
-
end
-
-
1
def hidden_input_value
-
then: 0
else: 0
then: 0
else: 0
return multiple ? Array(value).first : value if submit_selected_value
-
-
input_attributes[:value]
-
end
-
-
1
def hidden_input_form
-
then: 0
else: 0
return form if bind_hidden_input_to_form
-
-
nil
-
end
-
-
# TODO: The JS is missing receiving these options so that they start as selected
-
1
def selected_options
-
then: 0
if value.is_a?(Array)
-
formatted_value = value.map(&:to_s)
-
else: 0
all_options.filter { |o| formatted_value.include?(o.value.to_s) }
-
then: 0
elsif include_blank
-
[all_options.find { |o| o.value.to_s == value.to_s } || all_options.find { |o| blank_option?(o) }].compact
-
else: 0
else
-
[all_options.find { |o| o.value.to_s == value.to_s }].compact
-
end
-
end
-
-
1
def presented_selected_text
-
then: 0
else: 0
if selected_options.present?
-
selected_options.map(&:text).join(", ").gsub(/([\w\s]{40}).+/,'\1...')
-
else
-
nil
-
end
-
end
-
-
1
def options
-
root_options = @options
-
-
else: 0
then: 0
return root_options unless include_blank
-
-
[blank_option_struct, *root_options]
-
end
-
-
1
def all_options(root = options)
-
root.map { |o| [o, all_options(o.child_options)] }.flatten
-
end
-
-
1
def blank_option?(option)
-
option.value.blank? && (option.text.blank? || option.text == include_blank)
-
end
-
-
1
private
-
-
1
def blank_option_struct
-
Option.new(
-
value: "",
-
text: blank_option_text,
-
icon: nil,
-
tooltip: nil,
-
child_options: [],
-
attributes: {},
-
)
-
end
-
-
1
def blank_option_text
-
case include_blank
-
when: 0
when String
-
include_blank
-
when: 0
when true
-
""
-
else: 0
else
-
""
-
end
-
end
-
end
-
end
-
end
-
<%
-
def render_function(option, level = 0, parent_id: nil)
-
tag.div(
-
then: 0
else: 0
class: "lui-input-select__option #{blank_option?(option) ? 'lui-input-select__option--blank' : ''}",
-
role: "option",
-
data: {
-
"input-select2-target": :option,
-
"select2-target": :option,
-
then: 0
else: 0
action: !multiple ? "click->input-select2#select #{option[:attributes][:"data-action"]}" : "click->select2#select #{option[:attributes][:"data-action" ]}",
-
text: option.text,
-
value: option.value,
-
},
-
**option[:attributes]
-
) do
-
tag.div(class: "lui-input-select__option-wrapper") do
-
tag.div(class: "lui-input-select__option-text") do
-
then: 0
(multiple ? render(LooposUi::Inputs::Checkbox.new(
-
else: 0
then: 0
else: 0
state: option.value.in?(selected_options.map(&:value)) ? :checked : :unchecked
-
)) : "".html_safe) +
-
then: 0
else: 0
(tag.i(class: faviconize(option.icon)) if option.icon.present?) +
-
tag.span(role: "text") do
-
option.text.to_s
-
end +
-
then: 0
else: 0
(render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", size: "12", text: option.tooltip) if option.tooltip.present?)
-
end +
-
else: 0
then: 0
(tag.i(
-
then: 0
else: 0
then: 0
else: 0
class: "#{faviconize("fa-regular fa-check")} #{selected_options && selected_options.find { |o| o.value&.eql?(option.value) } ? '' : 'hidden!'}",
-
data: { "input-select2-target": "check" }
-
) unless multiple)
-
end
-
end +
-
tag.div(class: "lui-input-select__option-children", style: "padding-left: #{option_indent_size}px", data: { "select2-parent": option.value }) do
-
option.child_options.map { |sub_option| render_function(sub_option, level + 1) }.reduce(:+)
-
end
-
end
-
%>
-
<%= tag.div(
-
id: unique_id,
-
data: {
-
controller: "input-select2",
-
input_select2_input_outlet: "##{unique_id} .lui-inner-input",
-
input_select2_mode_value: mode,
-
input_select2_form_value: form,
-
input_select2_multiple_value: multiple,
-
input_select2_portal_target_value: portal_target,
-
input_select2_show_loading_on_submit_value: show_loading_on_submit,
-
}) do
-
%>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<div class="lui-input-select lui-input-select--<%= mode %> lui-select2--<%= multiple ? 'multiple' : 'single' %>--<%= show_actions_in_menu ? 'actions-menu' : 'no-actions-menu' %> relative <%= selected_options.any? { |option| blank_option?(option) } ? 'lui-input-select--blank-option-selected' : '' %>">
-
<%# FIXME: The name is changed so not to interfere with the hidden field below %>
-
<%= render LooposUi::Input.new(**input_attributes.merge(name: "_")) do |input| %>
-
<% input.with_right_addon do %>
-
<span class="flex flex-row items-center gap-2">
-
then: 0
else: 0
then: 0
else: 0
<i class="<%= selected_options.blank? ? 'opacity-0' : '' %> fa-solid fa-xmark cursor-pointer text-gray-400 lui-input-select__clear <%= clearable ? '' : 'hidden!' %>"
-
data-input-select2-target="clearButton"
-
data-action="click->input-select2#clear">
-
</i>
-
<div data-input-select2-target="icon">
-
<%# FIXME: Color should not be hardcoded %>
-
<%= render LooposUi::Icon.new(icon: "fa-regular fa-chevron-down", size: 12, color: "#212529") %>
-
</div>
-
</span>
-
<% end %>
-
<% end %>
-
<%= tag.div(
-
class: "lui-input-select__wrapper",
-
data: {
-
controller: "select2",
-
"input-select2-target": "menu",
-
select2_parent_selects_children_value: parent_selects_children,
-
select_target_id: unique_id,
-
select2_id: unique_id,
-
}
-
) do %>
-
<div class="lui-input-select__options" role="listbox">
-
then: 0
else: 0
<% if searchable %>
-
<%= render LooposUi::Inputs::Search.new(
-
name: "query",
-
value: nil,
-
placeholder: I18n.t('loopos_ui.inputs.select2.search_placeholder', default: 'Search...'),
-
expanded: true,
-
clearable: false,
-
data_attributes: {
-
action: "input->select2#handleSearch"
-
}
-
) %>
-
<div class="lui-association-overlay__divider"></div>
-
<% end %>
-
<div class="lui-association-overlay__results" data-select2-target="results">
-
<% options.each do |option| %>
-
<%= render_function(option, 0) %>
-
<% end %>
-
<div class="hidden lui-association-overlay__empty-search" data-select2-target="emptySearch">
-
<%= I18n.t('loopos_ui.inputs.select2.no_results', default: 'No results found') %>
-
</div>
-
</div>
-
then: 0
else: 0
<% if multiple && show_actions_in_menu %>
-
<div class="lui-select2__actions" data-select2-target="actions">
-
<%= render LooposUi::Button.new(
-
text: I18n.t('loopos_ui.inputs.select2.reset', default: 'Clear'),
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
tag_options: {
-
data: {
-
"action": "click->select2#clear"
-
},
-
type: :button
-
}
-
)
-
%>
-
<%= render LooposUi::Button.new(
-
text: I18n.t('loopos_ui.inputs.select2.apply', default: 'Apply'),
-
kind: :success,
-
type: :primary,
-
size: :tiny,
-
tag_options: {
-
type: :button,
-
data: {
-
"action": "click->select2#save"
-
},
-
}
-
)
-
%>
-
</div>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
<%= tag.div(data: { input_select2_target: "hiddenInputWrapper" }) do %>
-
<%= hidden_field_tag(
-
hidden_input_name,
-
hidden_input_value,
-
form: hidden_input_form,
-
data: {
-
input_select2_target: "hiddenInput"
-
})
-
%>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Inputs
-
1
class Text < LoopComponent
-
1
include BaseInputConcern
-
-
11
option :maxlength, default: -> { nil }
-
11
option :minlength, default: -> { nil }
-
-
1
def input_attributes
-
10
base_input_attributes.merge({
-
type: :text,
-
mode: mode,
-
input_attributes: {
-
maxlength: maxlength,
-
minlength: minlength,
-
}.merge(extra_input_attributes.presence || {}),
-
})
-
end
-
end
-
end
-
end
-
10
<div class="lui-text lui-text--<%= mode %> relative">
-
10
<%= render LooposUi::Input.new(**input_attributes) %>
-
</div>
-
1
module LooposUi
-
1
module Inputs
-
1
class TextArea < LoopComponent
-
1
include BaseInputConcern
-
-
7
option :rows, default: -> { nil }
-
7
option :cols, default: -> { nil }
-
7
option :maxlength, default: -> { nil }
-
7
option :minlength, default: -> { nil }
-
7
option :wrap, default: -> { "soft" }
-
7
option :resize, default: -> { "vertical" }
-
7
option :data_attributes, default: -> { {} }
-
-
1
def input_attributes
-
6
attrs = base_input_attributes
-
6
then: 2
else: 4
attrs.delete(:readonly) if readonly
-
6
attrs.merge({
-
input_attributes: {
-
rows: rows,
-
cols: cols,
-
maxlength: maxlength,
-
minlength: minlength,
-
wrap: wrap,
-
style: "resize: #{resize}",
-
data: { textarea_target: "input" }.merge(data_attributes),
-
readonly: readonly,
-
},
-
type: "textarea",
-
})
-
end
-
end
-
end
-
end
-
6
<div data-controller="textarea" class="lui-text_area">
-
6
<%= render LooposUi::Input.new(**input_attributes) %>
-
</div>
-
1
module LooposUi
-
1
module Item
-
1
class StateLabel < LoopComponent
-
1
attr_reader :item
-
-
1
class ItemStruct < Dry::Struct
-
1
attribute? :macro_name, Types::Coercible::String.optional
-
# FIXME: temporarily permit nil values
-
1
attribute? :macro_class, Types::Coercible::String.optional
-
1
attribute? :macro_kind, Types::Coercible::String.optional
-
1
attribute? :macro_icon, Types::Coercible::String.optional
-
1
attribute? :micro_name, Types::Coercible::String.optional
-
# FIXME: temporarily permit nil values
-
1
attribute? :micro_class, Types::Coercible::String.optional
-
1
attribute? :micro_kind, Types::Coercible::String.optional
-
1
attribute? :micro_icon, Types::Coercible::String.optional
-
-
# rubocop:disable Style/ClassMethodsDefinitions
-
1
def self.build_from_item(item)
-
# Sometimes we construct the ItemStruct directly, in this case
-
# the builder will already have the ItemStruct instance
-
116
then: 0
else: 116
if item.is_a?(self)
-
return item
-
end
-
-
116
args = schema.keys.each_with_object({}) do |key, args|
-
928
value = item.send(key.name)
-
# FIXME: temporarily remove nil check
-
# raise TypeError.new("[LoopOS UI] Item::StateLabel: Item method #{key.name} cannot return nil") if value.nil?
-
-
928
then: 696
else: 232
args[key.name] = value if value.present?
-
end
-
-
116
new(args)
-
end
-
-
1
def self.from_core_client_item(item)
-
new({
-
micro_class: item.dig(:state_badge, :ui_micro_class),
-
micro_name: item.dig(:state_badge, :micro_text),
-
micro_kind: item.dig(:state_badge, :ui_micro_class),
-
micro_icon: item.dig(:state_badge, :icon),
-
-
macro_class: item.dig(:state_badge, :macro_class),
-
macro_name: item.dig(:current_block, :name),
-
macro_kind: item.dig(:state_badge, :macro_class),
-
macro_icon: item.dig(:state_badge, :macro_icon),
-
})
-
end
-
-
# rubocop:enable Style/ClassMethodsDefinitions
-
end
-
-
1
option :item, Types.Constructor(ItemStruct, ItemStruct.method(:build_from_item))
-
113
option :macro_condensed, Types::Bool, default: -> { false }
-
113
option :micro_condensed, Types::Bool, default: -> { false }
-
-
1
def initialize(...)
-
116
super
-
-
116
@leading_text = item.macro_name
-
116
then: 116
else: 0
@leading_color = item.macro_class || item&.macro_kind
-
116
@trailing_text = item.micro_name
-
116
then: 116
else: 0
@trailing_color = item.micro_class || item&.micro_kind
-
116
@leading_icon = item.macro_icon
-
116
@trailing_icon = item.micro_icon
-
end
-
-
1
def unique_id
-
@unique_id ||= SecureRandom.uuid
-
end
-
end
-
end
-
end
-
116
<div class="inline-flex">
-
4
then: 4
else: 112
<%= render LooposUi::Tooltip.new do |tooltip| %>
-
4
<%= render LooposUi::DoubleStateLabel.new(
-
leading_text: @leading_text.to_s,
-
leading_color: @leading_color,
-
trailing_text: @trailing_text.to_s,
-
trailing_color: @trailing_color,
-
leading_icon: @leading_icon,
-
trailing_icon: @trailing_icon,
-
light: true,
-
4
) %>
-
116
<% end if macro_condensed || micro_condensed %>
-
116
<%= render LooposUi::DoubleStateLabel.new(
-
leading_text: @leading_text.to_s,
-
leading_color: @leading_color,
-
leading_condensed: macro_condensed,
-
trailing_text: @trailing_text.to_s,
-
trailing_color: @trailing_color,
-
leading_icon: @leading_icon,
-
trailing_icon: @trailing_icon,
-
trailing_condensed: micro_condensed,
-
light: true,
-
116
) %>
-
</div>
-
1
module LooposUi
-
1
class ItemValue < LoopComponent
-
3
option :accept_value_btn, default: -> { true }
-
3
option :cancel_value_btn, default: -> { true }
-
3
option :proposal, default: -> { {} }
-
-
3
option :reason_edit_value, default: -> { "" }
-
3
option :proposal_kind, default: -> { "in" }
-
1
option :price_details
-
1
option :status
-
3
option :cancel_value_btn_text, default: -> { "acceptBtnText" }
-
-
1
private
-
-
1
def accept_value_btn?
-
accept_value_btn || false
-
end
-
-
1
def cancel_value_btn?
-
cancel_value_btn || false
-
end
-
end
-
end
-
2
<%= react_component("ItemValue",
-
{
-
proposal: proposal,
-
priceEstimation: price_details,
-
state: status,
-
reasonEditValue: reason_edit_value,
-
proposalKind: proposal_kind,
-
locale: I18n.locale,
-
}
-
) %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Label < LoopComponent
-
1
attr_accessor :text, :icon, :condensed
-
1
attr_reader :text_color, :bg_color
-
-
1
def initialize(text: nil, icon: nil, color: nil, count: nil, condensed: false, system_args: {})
-
246
@text = text
-
246
@icon = icon
-
246
then: 246
else: 0
@color = color&.to_sym
-
246
@count = count
-
246
@condensed = condensed
-
246
@system_args = system_args
-
-
246
then: 0
else: 246
@text_color, @bg_color = color if color.is_a?(Array) && color.size == 2
-
end
-
-
1
def system_args
-
246
@system_args.slice(:data, :id)
-
end
-
-
1
def text_too_long?
-
246
text.present? && text.length >= 33
-
end
-
-
1
def styles
-
246
then: 0
else: 246
return if text_color.nil? || bg_color.nil?
-
-
246
<<~CSS.squish
-
color: #{text_color};
-
background-color: #{bg_color};
-
CSS
-
end
-
end
-
end
-
492
<%= tag.span(class: "lui-label", style: styles, **system_args) do %>
-
246
then: 0
else: 246
<%= render LooposUi::Tooltip.new(title: text) if text_too_long? %>
-
246
<% case icon %>
-
when: 92
<% when "core", "submission", "hubs", "validation", "handling", "exits", "impact" %>
-
92
<%= helpers.app_svg(app: icon, size: :tiny) %>
-
when: 0
<% when "decision_ai" %>
-
<%= helpers.app_svg(app: "ai_decision", size: :tiny) %>
-
else: 154
<% else %>
-
154
then: 148
else: 6
<%= tag.i(class: "#{icon} lui-label__icon") if icon.present? %>
-
<% end %>
-
<%# WARNING: Please do not remove the `lui--label` data attribute, it is used to update the text %>
-
246
then: 240
else: 6
<%= tag.span(text, class: "lui-label__text", data: { "lui--label": "content" }) if !condensed %>
-
246
then: 0
else: 246
<%= render LooposUi::Counter.new(count: @count, kind: @color) if @count %>
-
<% end %>
-
<div class="app">
-
<div class="los-manager-header" id="topbar">
-
then: 0
else: 0
<%= @topbar if @user_signed_in %>
-
</div>
-
<div class="app__main-container">
-
then: 0
else: 0
<%= @sidebar if @user_signed_in %>
-
<div class="app__content-wrapper">
-
<%= image_tag(@background, :class => "app__background") %>
-
<div class="app__content">
-
<%= @app_content %>
-
</div>
-
</div>
-
</div>
-
-
then: 0
else: 0
<% if @user_signed_in %>
-
<%= turbo_stream_from("flash_container_#{@current_user.id}") %>
-
<div id="notice_<%= @current_user.id %>"></div>
-
<% end %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class LayoutComponent < ViewComponent::Base
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(topbar:, sidebar:, app_content:, user_signed_in:, current_user:, background: )
-
LooposUi.logger.warn("DEPRECATION WARNING: LooposUi::LayoutComponent is deprecated. Please use LooposUi::AppLayoutComponent instead.")
-
-
@topbar = topbar
-
@sidebar = sidebar
-
@app_content = app_content
-
@user_signed_in = user_signed_in
-
@current_user = current_user
-
@background = background
-
end
-
end
-
end
-
1
module LooposUi
-
1
class LayoutLoading < LoopComponent
-
# NOTE: This is a internal usage component, it's not intended to be used outside of the LooposUi gem.
-
end
-
end
-
<div class="lui-layout-loading">
-
<div class="lui-layout-loading__content">
-
<%= render LooposUi::Loadings::Skeleton.new(full: false) %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class Link < LoopComponent
-
1
option :url, type: Types::String
-
1
option :text, optional: true, type: Types::String.optional
-
3
option :data, default: -> { {} }, type: Types::Hash
-
3
option :turbo_target, default: -> { "lui-main-layout" }, type: Types::String.optional
-
3
option :turbo_action, default: -> { "navigate" }, type: Types::String.optional
-
-
1
def merged_data_attributes
-
2
{ turbo_target: turbo_target, turbo_action: turbo_action }.merge(data)
-
end
-
-
1
use_extra_options
-
end
-
end
-
2
<div class='lui-link'>
-
4
<%= tag.a(href: url, data: merged_data_attributes, **extra_options) do %>
-
2
then: 0
<%if content.present? %>
-
else: 2
<%= content%>
-
2
then: 0
<%elsif text%>
-
<%= text%>
-
else: 2
<%else %>
-
2
<%= url%>
-
<%end%>
-
<%end%>
-
2
</div>
-
-
1
module LooposUi
-
1
class LoadingAi < LoopComponent
-
-
end
-
end
-
<div class="lui-loading_ai" >
-
<%= image_tag('LoopOS_AI.svg') %>
-
</div>
-
1
module LooposUi
-
1
module Loadings
-
1
class Skeleton < LoopComponent
-
1
option :full, optional: true, default: -> { false }
-
-
1
option :n_rows, Types::Integer, default: -> { 4 }
-
-
1
class Bar < LoopComponent
-
1
option :width, Types::String, default: -> { "100%" }
-
-
1
erb_template <<-ERB.squish
-
<div class="lui-skeleton__bar" style="width: <%= width %>"></div>
-
ERB
-
end
-
end
-
end
-
end
-
then: 0
else: 0
<div class="lui-skeleton <%= full ? "lui-skeleton--full" : "" %>">
-
<% n_rows.times.each do %>
-
<div class="lui-skeleton__item">
-
<%= render LooposUi::Loadings::Skeleton::Bar.new %>
-
</div>
-
<% end %>
-
-
<div class="lui-skeleton__footer">
-
<div class="lui-skeleton__bar--footer"></div>
-
<div class="lui-skeleton__bar--footer lui-skeleton__bar--footer--invisible"></div>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
require "cgi"
-
-
1
module LooposUi
-
1
class Log < LoopComponent
-
1
renders_one :source, types: {
-
entity_token: { renders: EntityToken, as: :entity_token_source },
-
entity: { renders: ->(entity:) { entity }, as: :source },
-
}
-
-
1
renders_one :title_token, types: {
-
entity_token: { renders: EntityToken, as: :title_entity_token },
-
entity: { renders: ->(entity:) { entity }, as: :title_token },
-
manual: { renders: ->(text:) { text }, as: :title_token_manual },
-
}
-
-
# Not meant to be used directly
-
# We'd prefix this with _ but ViewComponent breaks if we do that
-
# Again, not part of the public API, use with_expand instead and with_button instead
-
1
renders_one :private_expand
-
1
renders_many :private_buttons, types: {
-
button: { renders: Button, as: :private_button },
-
2
button_manual: { renders: ->(&block) { capture(&block) }, as: :private_button_manual },
-
}
-
-
1
option :error, Types::Bool, default: -> { false }
-
1
mod :error
-
-
1
option :user, Types::String, optional: true
-
1
option :date, Types::TDate
-
-
4
option :log_class, Types::String, optional: true, default: -> { "item" }
-
1
option :block,
-
Types::Hash.schema(
-
name: Types::String,
-
kind: Types::String,
-
url: Types::String,
-
),
-
optional: true
-
-
1
NilLog = new(error: true, user: nil, date: Time.current).with_content("Failed to show this log.")
-
-
# Factory method to create a Log from any source (auto-detects format)
-
1
def self.from_any_source(source)
-
2
log = LooposUi::Log::Factories.create_from_any_source(source)
-
2
else: 2
then: 0
raise "Failed to build log from source: #{source}" unless log
-
-
2
log
-
end
-
-
1
def before_render
-
2
then: 2
else: 0
@defer_render_blocks&.each do |block|
-
2
block.call(self, @view_context)
-
end
-
2
@defer_render_blocks = nil
-
end
-
-
1
def defer_render(&block)
-
2
@defer_render_blocks ||= []
-
2
@defer_render_blocks << block
-
end
-
-
1
def initialize(...)
-
2
super(...)
-
rescue => e
-
LooposUi.logger.error("Error parsing log: #{e.message}")
-
LooposUi.logger.error(e.backtrace.join("\n"))
-
raise e
-
end
-
-
1
def parsed_content
-
2
then: 2
else: 0
split_text_to_words(content&.to_s)
-
end
-
-
1
def parsed_expand_content
-
split_text_to_words(private_expand.to_s)
-
end
-
-
1
def with_expand(&block)
-
with_private_expand(&block)
-
with_button(**expand_button_args)
-
end
-
-
1
def with_expand_content(content)
-
then: 0
if view_context.present?
-
with_expand do
-
view_context.capture { content }
-
end
-
else: 0
else
-
defer_render do |log, view_context|
-
log.with_expand do
-
view_context.capture { content }
-
end
-
end
-
end
-
end
-
-
1
def with_button(*args, **kwargs, &block)
-
2
with_private_button(*args, **default_button_args.merge(kwargs), &block)
-
end
-
-
1
def with_button_manual(*args, **kwargs, &block)
-
2
with_private_button_manual(&block)
-
end
-
-
1
def expand_button_args
-
{
-
**default_button_args,
-
text: t("loopos_ui.log.script_log.show_details"),
-
trailing_icon: :chevron_down,
-
tag_options: {
-
data: {
-
action: "click->log#showMore:prevent log:toggleExpand->lui--button#rotateTrailingIcon",
-
log_target: "toggleExpand",
-
},
-
},
-
}
-
end
-
-
1
def default_button_args
-
8
@default_button_args ||= {
-
size: :tiny,
-
kind: :neutral,
-
type: :secondary,
-
}
-
end
-
-
1
def time
-
date.strftime("%H:%Mh")
-
end
-
-
1
def user_string
-
2
then: 2
if user.present?
-
2
user
-
else: 0
else
-
t("loopos_ui.log.script_log.system")
-
end
-
end
-
-
1
def block_icon(kind)
-
normalized_kind = kind.to_s.sub(/-rails\z/, "")
-
-
flow_block_icons = {
-
# Tool blocks
-
"tag" => "fa-solid fa-tag",
-
"remove_tag" => "fa-regular fa-tag",
-
"auto_validation" => "fa-solid fa-hand-holding-dollar",
-
"auto_accept_proposal" => "fa-regular fa-circle-check",
-
"script" => "fa-regular fa-code",
-
"script_advanced" => "fa-regular fa-code",
-
"item_value_agreed" => "fa-regular fa-circle-check",
-
"impact" => "fa-solid fa-recycle",
-
# Decision blocks
-
"decision_custom" => "fa-solid fa-split",
-
"decision_script" => "fa-solid fa-split",
-
"decision_transport" => "fa-solid fa-split",
-
"decision_multirule" => "fa-solid fa-split",
-
"decision_ai" => "decision_ai",
-
# Service/other blocks
-
"marketplace" => "fa-regular fa-store",
-
"transport" => "fa-regular fa-truck",
-
"outgoing_payment" => "fa-regular fa-credit-card",
-
"incoming_payment" => "fa-regular fa-envelope-open-dollar",
-
"sms" => "fa-regular fa-message-sms",
-
"invoice" => "fa-regular fa-file-invoice",
-
"email" => "fa-regular fa-envelope",
-
}
-
-
then: 0
else: 0
return flow_block_icons[normalized_kind] if flow_block_icons.key?(normalized_kind)
-
-
case normalized_kind
-
when: 0
when "message"
-
"fa-regular fa-message-sms"
-
when: 0
when "payment"
-
"fa-regular fa-credit-card"
-
when: 0
when "shipping"
-
"fa-regular fa-truck"
-
when: 0
when "submission_extra"
-
"submission"
-
else: 0
else
-
normalized_kind
-
end
-
end
-
-
1
def log_class_icon
-
2
else: 0
case log_class
-
when: 0
when "transition"
-
:double_arrow
-
when: 0
when "email"
-
:mail
-
when: 0
when "sms"
-
:message
-
when: 0
when "outgoing_payment"
-
:payment_arrow_up
-
when: 0
when "incoming_payment"
-
:payment_arrow_down
-
when: 0
when "shipping"
-
:local_shipping
-
when: 0
when "invoice"
-
:receipt_long
-
when: 0
when "item_value"
-
:money_range
-
when: 2
when "item"
-
2
:package_2
-
when: 0
when "privacy"
-
:visibility_lock
-
end
-
end
-
-
1
private
-
-
1
def split_text_to_words(source, sanitize: false)
-
2
html = if sanitize
-
# Sanitization is removing data attrs, which breaks a lot
-
then: 0
# of our components
-
ActionController::Base.helpers.sanitize(
-
source,
-
tags: ["div", "strong", "em", "b", "i", "u", "span", "a"],
-
attributes: ["href", "class", "style", "id", "title", "target", "rel"] + [/data\-.*/],
-
)
-
else: 2
else
-
2
maybe_unescape_escaped_html(source)
-
end
-
-
2
fragment = Nokogiri::HTML::DocumentFragment.parse(html)
-
-
2
text_nodes = fragment.xpath("./text()")
-
-
2
text_nodes.each do |node|
-
2
node_content = node.content.to_s
-
2
words = node_content.split(/\s/).reject(&:blank?)
-
2
then: 0
else: 2
next if words.blank?
-
-
2
words = words.each_with_index.map do |word, i|
-
8
is_last_word = i == words.length - 1
-
8
should_append_space = !is_last_word || node_content.match?(/\s\z/)
-
8
then: 6
else: 2
word = "#{word.strip} " if should_append_space
-
-
8
new_node = Nokogiri::XML::Node.new("div", fragment).tap do |span|
-
8
span["class"] = "lui-log__word"
-
8
span.content = word
-
end
-
-
8
node.add_previous_sibling(new_node)
-
end
-
2
node.remove # remove the original text node
-
end
-
-
2
fragment.to_html.html_safe
-
end
-
-
# Some upstream paths end up storing HTML as escaped entities (e.g. "<div ...>").
-
# Nokogiri would then treat it as plain text, and our "word split" would break markup.
-
# If it looks like escaped tags, unescape (up to a couple times) before parsing.
-
1
def maybe_unescape_escaped_html(source)
-
2
else: 2
then: 0
return source unless source.is_a?(String)
-
-
# Quick heuristic: detect escaped HTML tags like "<div" or "</span".
-
2
else: 0
then: 2
return source unless source.match?(%r{</?\s*[a-zA-Z][a-zA-Z0-9:-]*})
-
-
decoded = source
-
2.times do
-
else: 0
then: 0
break unless decoded.match?(%r{</?\s*[a-zA-Z][a-zA-Z0-9:-]*})
-
-
decoded = CGI.unescapeHTML(decoded)
-
end
-
-
decoded
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Log
-
1
module Factories
-
# Registry for all available factories
-
1
class << self
-
1
def factories
-
2
Base.subclasses
-
end
-
-
1
def find_factory_for(source)
-
2
else: 2
then: 0
return unless source
-
-
6
then: 2
else: 0
factories&.find { |factory| factory.can_create?(source) }
-
end
-
-
1
def create_from_any_source(source, factory_class: nil)
-
2
klass = factory_class || find_factory_for(source)
-
2
else: 2
then: 0
return unless klass
-
-
2
factory = klass.new(source)
-
2
factory.try_create
-
end
-
-
# Auto-load all factories in the factories directory
-
1
def load_all_factories
-
1
factories_dir = File.dirname(__FILE__)
-
1
Dir.glob(File.join(factories_dir, "factories", "*.rb")).each do |file|
-
3
require file
-
end
-
end
-
end
-
-
# Load all factories when this module is loaded
-
1
load_all_factories
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Log
-
1
module Factories
-
# Base factory class that all log factories should inherit from
-
1
class Base
-
1
def initialize(source, script: nil)
-
2
@source = source
-
2
@script = script
-
end
-
-
1
def self.can_create?
-
raise NotImplementedError, "#{self} must implement can_create?"
-
end
-
-
1
def create
-
raise NotImplementedError, "#{self} must implement create"
-
end
-
-
1
def try_create
-
2
create
-
rescue => e
-
LooposUi.logger.error("Error creating log: #{e.message}")
-
LooposUi.logger.error(e.backtrace.join("\n"))
-
-
then: 0
if Rails.env.development?
-
raise e
-
else: 0
else
-
LooposUi::Log::NilLog
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Log
-
1
module Factories
-
# This builder expects the hash that the core item_logs_presenter returns
-
# and adapts it to the log component
-
# The expected schema of the hash is defined in the can_build? method
-
1
class CoreItemLogPresenterHash < Base
-
1
def self.valid_block?(block)
-
else: 0
then: 0
return false unless block.is_a?(Hash)
-
-
block = block.with_indifferent_access
-
[:name, :kind, :url].all? { |key| block[key].present? }
-
end
-
-
1
def self.can_create?(source)
-
else: 0
then: 0
return false unless source.is_a?(::Hash)
-
-
source = source.with_indifferent_access
-
[
-
:time, :author, :app_instance, :message, :error,
-
].all? { |key| source.key?(key) } &&
-
source[:app_instance].is_a?(Hash) &&
-
source[:app_instance][:name].present? &&
-
source[:app_instance][:kind].present?
-
rescue => e
-
LooposUi.logger.error("Error creating CoreItemLogPresenterHash: #{e.message}")
-
false
-
end
-
-
1
def initialize(source)
-
super(source)
-
@source = source.with_indifferent_access
-
end
-
-
1
def create
-
log_options = {
-
error: @source[:error],
-
user: @source[:author],
-
date: @source[:time],
-
}
-
-
then: 0
else: 0
log_options[:log_class] = @source[:log_class] if @source[:log_class].present?
-
-
then: 0
else: 0
if valid_block?(@source[:block])
-
log_options[:block] = {
-
name: @source[:block][:name],
-
kind: @source[:block][:kind],
-
url: @source[:block][:url],
-
}
-
end
-
-
log = LooposUi::Log.new(**log_options)
-
-
log.with_content(@source[:message])
-
-
then: 0
else: 0
log.with_title_entity_token(text: @source[:title]) if @source[:title].present?
-
-
then: 0
else: 0
log.with_expand_content(@source[:extra_message]) if @source[:extra_message].present?
-
-
add_source(log)
-
-
log
-
end
-
-
1
private
-
-
1
def valid_block?(block)
-
self.class.valid_block?(block)
-
end
-
-
1
def add_source(log)
-
kind, name = @source[:app_instance].fetch_values(:kind, :name)
-
-
then: 0
if kind == "neutral"
-
log.with_entity_token_source(text: name, color: :general)
-
else: 0
else
-
log.with_source(entity: LooposUi::Entities::AppInstance.from_hash(@source[:app_instance]))
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Log
-
1
module Factories
-
1
class ScriptLog < Base
-
1
def self.can_create?(source)
-
2
defined?(LoopOs::ScriptLog) && source.is_a?(LoopOs::ScriptLog)
-
end
-
-
1
def create
-
2
@log = LooposUi::Log.new(
-
date: @source.created_at,
-
user: @source.user_identification,
-
error: @source.level.to_s == "error",
-
)
-
-
2
@log.with_content(@source.name)
-
-
2
then: 2
else: 0
add_download_button if @source.files.present?
-
2
then: 0
else: 2
add_retry_button if should_add_retry_button?
-
2
add_info_modal
-
2
add_source
-
-
2
@log
-
end
-
-
1
def initialize(...)
-
2
super(...)
-
end
-
-
1
private
-
-
1
def should_add_retry_button?
-
2
@script.present? && script_proxy? && has_input? && @script.has_retry_capability?
-
end
-
-
1
def script_proxy?
-
@script.is_a?(LoopOs::Scripts::ScriptProxy) && @script.script?
-
end
-
-
1
def has_input?
-
@source.input_data.present? || (@source.respond_to?(:input_files) && @source.input_files.attached?)
-
end
-
-
1
def add_retry_button
-
@log.defer_render do |log, view_context|
-
log.with_button_manual do
-
view_context.render(LooposUi::Modal.new(
-
id: "retry_script_modal_#{@source.id}",
-
title: t(".retry_script_modal.title"),
-
description: t(".retry_script_modal.description"),
-
)) do |modal|
-
modal.with_trigger do
-
view_context.render(LooposUi::Button.new(
-
text: t(".retry_script"),
-
icon: "fa-regular fa-rotate-right",
-
**@log.default_button_args,
-
))
-
end
-
-
modal.with_primary_action(
-
text: t(".retry_script_modal.confirm"),
-
tag_options: {
-
form: "retry_form_#{@source.id}",
-
type: "submit",
-
data: { type: "submit" },
-
},
-
)
-
-
view_context.content_tag(:div, style: "display: none;") do
-
view_context.form_with(url: @script.retry_path_for(@source), method: :post, id: "retry_form_#{@source.id}", data: { turbo_frame: "_top", turbo: true }) do |form|
-
form.submit
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
def add_source
-
2
@log.with_entity_token_source(text: t(".system"))
-
end
-
-
1
def add_download_button
-
2
@log.with_button(
-
**download_button_args,
-
)
-
end
-
-
1
def t(key, **kwargs)
-
10
LooposUi::Log.t(key, scope: [:loopos_ui, :log, :script_log], **kwargs)
-
end
-
-
1
def add_info_modal
-
2
@log.defer_render do |log, view_context|
-
2
log.with_button_manual do
-
2
view_context.render(LooposUi::Modal.new(title: t(".modal_title"), modal_width: 1080, show_footer: false)) do |modal|
-
2
modal.with_trigger do
-
2
view_context.render(LooposUi::Button.new(
-
text: t(".show_details"),
-
**@log.default_button_args,
-
))
-
end
-
-
2
then: 2
else: 0
modal.with_primary_action(**download_button_args) if @source.files.present?
-
-
2
view_context.render(LooposUi::Log::Script::InfoModalBody.new(log: @source))
-
end
-
end
-
end
-
end
-
-
1
def download_button_args
-
{
-
4
text: t(".download_file"),
-
icon: :download,
-
href: @source.files.first.url,
-
tag_options: { turbo: false },
-
**@log.default_button_args,
-
}
-
end
-
end
-
end
-
end
-
end
-
2
<%= tag.div(class: classes, data: {
-
controller: "log",
-
log_expanded_copy_text_value: t("loopos_ui.log.script_log.show_details"),
-
2
log_collapsed_copy_text_value: t("loopos_ui.log.script_log.hide_details") }) do %>
-
4
<%= tag.div(class: "lui-log__main") do %>
-
4
<%= tag.div(class: "lui-log__content") do %>
-
4
<%= tag.div(class: "lui-log__content__icon") do %>
-
2
<%= render LooposUi::Tooltip.new(title: I18n.t("admin.items.logs.log_classes.#{log_class}", default: log_class.to_s.humanize)) %>
-
2
<%= render LooposUi::MIcon.new(log_class_icon, size: 20) %>
-
<% end %>
-
4
<%= tag.div(class: "lui-log__content__header") do %>
-
4
<%= tag.div(class: "lui-log__content__header__title", data: { log_target: "content" }) do %>
-
2
<%= parsed_content %>
-
<% end %>
-
4
<%= tag.div(class: "lui-log__content__header__description") do %>
-
2
<%= t("loopos_ui.log.script_log.by") %>
-
2
<%= user_string %>
-
<% end %>
-
2
then: 0
else: 2
<%= tag.div(class: "lui-log__expand-content", data: { log_target: :expand }) do %>
-
<%= tag.div(class: "lui-log__expand-inner") do %>
-
<%= parsed_expand_content %>
-
<% end %>
-
2
<% end if private_expand? %>
-
4
<%= tag.div(class: "lui-log__buttons") do %>
-
2
<% private_buttons.each do |button| %>
-
4
<%= button %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
4
<%= tag.div(class: "lui-log__source_content") do %>
-
2
<%= date.strftime("%d/%m/%Y %H:%M") %>
-
2
then: 0
else: 2
<% if block.present? %>
-
then: 0
<% if block.dig(:url).present? %>
-
<%= tag.a(href: block[:url], class: "lui-log__source_content__link", target: "_blank", data: { turbo: "false" }) do %>
-
<%= render LooposUi::StateLabel.new(
-
text: block[:name],
-
color: block[:kind],
-
icon: block_icon(block[:kind])) %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::StateLabel.new(
-
text: block[:name],
-
color: block[:kind],
-
icon: block_icon(block[:kind])) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%# TODO: this is copied from the script log modal, not yet fully migrated to UI %>
-
2
<div class="flex flex-col w-full gap-2">
-
2
<span class="text-[14px]"><%= I18n.t('loop_os_scripts.logs.parameters') %></span>
-
<%
-
2
then: 0
else: 2
then: 0
else: 2
has_input_data = log.input_data&.keys&.present?
-
2
has_input_files = log.respond_to?(:input_files) && log.input_files.attached?
-
%>
-
2
<% if has_input_data || has_input_files %>
-
then: 0
<%
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
input_data_count = has_input_data ? log.input_data&.keys&.size : 0
-
then: 0
else: 0
input_file_count = has_input_files ? log.input_files.length : 0
-
total_count = input_data_count + input_file_count
-
then: 0
else: 0
grid_class = total_count <= 6 ? "logs__modal__grid-layout logs__modal__grid-layout--one-row" : "logs__modal__grid-layout logs__modal__grid-layout--scrollable"
-
%>
-
<div class="<%= grid_class %>">
-
then: 0
else: 0
<% if has_input_data %>
-
<% log.input_data.keys.each do |input_field| %>
-
<div class="logs__modal__grid-layout--item">
-
<span><strong><%= input_field %></strong></span>
-
<span><%= log.input_data.dig(input_field) %></span>
-
</div>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<% if has_input_files %>
-
<% log.input_files.each do |file| %>
-
<div class="logs__modal__grid-layout--item">
-
<span><strong><%= file.blob.metadata['param_name'] || I18n.t('loop_os_scripts.logs.file') %></strong></span>
-
<span>
-
then: 0
else: 0
<% file_url = file.respond_to?(:url) ? file.url : url_for(file) %>
-
<%= link_to file.blob.filename, file_url, target: "_blank", rel: "noopener noreferrer", class: "text-blue-600 hover:underline", download: file.blob.filename.to_s, data: { turbo: false } %>
-
</span>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
else: 2
<% else %>
-
2
-
-
<% end %>
-
2
</div>
-
<div class="flex flex-col w-full gap-2">
-
2
<span class="text-[14px]"><%= I18n.t('loop_os_scripts.logs.time_running') %></span>
-
2
then: 0
else: 2
then: 0
else: 0
then: 0
<% if log.extra_data&.is_a?(Hash) && log.extra_data&.dig("time_running").present? %>
-
else: 2
then: 0
else: 0
<span class="text-black text-[12px]"><%= distance_of_time_in_words(0, log.extra_data&.dig("time_running").to_i.seconds) %></span>
-
2
then: 0
else: 2
then: 0
else: 0
then: 0
else: 0
then: 0
<% elsif log.extra_data&.is_a?(Array) && log.extra_data.last&.dig("extra")&.dig("time_running").present? %>
-
then: 0
else: 0
then: 0
else: 0
<span class="text-black text-[12px]"><%= distance_of_time_in_words(0, log.extra_data.last&.dig("extra")&.dig("time_running").to_i.seconds) %></span>
-
else: 2
<% else %>
-
2
-
-
<% end %>
-
2
</div>
-
<div class="flex flex-col w-full gap-2">
-
2
<span class="text-[14px]"><%= I18n.t('loop_os_scripts.logs.results') %></span>
-
2
<%= helpers.turbo_stream_from("script_log_data_#{log.id}") %>
-
<div class="flex flex-col w-full h-full gap-2" style="max-height: 35vh;">
-
2
then: 0
<% if log.extra_data.present? %>
-
<pre id="extra_data_loop_os_script_log_<%= log.id %>" class="text-xs text-gray-800 bg-white p-4 rounded border overflow-x-auto"><%= JSON.pretty_generate(log.extra_data) %></pre>
-
else: 2
<% else %>
-
2
<div class="text-center text-gray-500 py-8">
-
2
<p><%= I18n.t('loop_os_scripts.logs.empty_results') %></p>
-
</div>
-
<% end %>
-
2
</div>
-
</div>
-
1
module LooposUi
-
1
class Log
-
1
module Script
-
1
class InfoModalBody < LoopComponent
-
1
option :log
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class LogList < LoopComponent
-
1
class Config < Dry::Struct
-
1
MAX_ITEMS = 15
-
2
then: 0
else: 1
attribute :max_items, Types::Integer.constructor { |value| value.to_i <= 0 ? MAX_ITEMS : value.to_i }.default(MAX_ITEMS)
-
1
attribute :sort_direction, Types::Symbol.default(:desc).enum(:asc, :desc)
-
1
attribute :has_pagination, Types::Bool.default(true)
-
end
-
-
1
option :id, Types::Coercible::String.constructor { |id|
-
"lui-log-list-#{id}"
-
}
-
-
1
option :path, Types::String, optional: true
-
1
option :config,
-
Types::Hash.schema(
-
3
Config.schema.keys.to_h { |i| [i.name, i.type] },
-
) | Config,
-
default: -> { Config.new.to_h }
-
-
1
option :logs, Types::Array.of(Types::Instance(LooposUi::Log)), default: -> { [] }
-
-
1
option :items, Types::Interface(:first), optional: true # enumerable
-
1
option :factory, optional: true # TODO: type checking
-
1
option :pagy, optional: true
-
1
option :pagination,
-
Types::Hash.schema(
-
page: Types::Coercible::Integer.default(1),
-
items: Types::Coercible::Integer.default(Config.new.max_items),
-
count: Types::Coercible::Integer,
-
),
-
optional: true
-
-
1
def initialize(...)
-
super(...)
-
-
validate_factory!
-
-
warn_if_missing_path
-
-
create_pagy
-
-
then: 0
else: 0
@logs = build_logs_from_items if @items.present?
-
-
then: 0
else: 0
config_hash = @config.is_a?(Config) ? @config.to_h : @config
-
-
then: 0
else: 0
if config_hash[:has_pagination] && @pagy.blank?
-
raise "pagination parameters (pagy or pagination) must be present in the options if you want to use pagination"
-
end
-
-
@config = Config.new(
-
**config_hash,
-
has_pagination: config_hash[:has_pagination] && @pagy.present? && (@pagy.count > @logs.count),
-
then: 0
else: 0
max_items: @pagy.present? && @pagy.count > 0 ? @pagy.count : config_hash[:max_items],
-
)
-
-
build_groups
-
end
-
-
1
def build_logs_from_items
-
@factory ||= LooposUi::Log::Factories.find_factory_for(@items.first)
-
then: 0
else: 0
raise "No factory found for items: #{@items.first}" if @factory.nil?
-
-
@items.map do |item|
-
@factory.new(item).try_create
-
end
-
end
-
-
1
def self.from_any_source(source, id:, config: {}, **options)
-
list_options = options.dup
-
options = list_options.extract!(:page)
-
-
then: 0
else: 0
if config[:has_pagination] && !options.key?(:page)
-
raise "page must be present in the options if you want to use pagination"
-
end
-
-
config = Config.new(**config)
-
config = Config.new(**config.to_h, has_pagination: config.has_pagination && options[:page].present?)
-
-
log_list = LooposUi::LogList::Factories.create_from_any_source(
-
source,
-
options: options,
-
list_options: {
-
id: id,
-
config: config,
-
**list_options,
-
},
-
)
-
else: 0
then: 0
raise "Failed to build log from source: #{source}" unless log_list
-
-
log_list
-
end
-
-
1
def render?
-
true
-
end
-
-
1
def groups
-
@groups ||= [] # We could use a Set, but there won't be that many groups, so it's faster
-
end
-
-
1
def add_log(log)
-
log_date = log.date.to_date
-
group = groups.find { |group| group.date == log_date }
-
then: 0
if group.nil?
-
group = LooposUi::LogList::Group.new(date: log_date, logs: [log])
-
groups << group
-
else: 0
else
-
group.logs << log
-
end
-
-
group
-
end
-
-
1
private
-
-
1
def build_groups
-
then: 0
else: 0
if @logs.count > config.max_items
-
LooposUi.logger.warn(<<~LOG.squish)
-
LOOPOS_UI: Performance warning: LogList is being passed more than #{config.max_items} logs (#{@logs.count}).
-
This will impact performance as it forces the component to sort the logs in memory.
-
Consider passing the data already sorted and limited to #{config.max_items} items.
-
Backtrace:
-
#{caller.join("\n")}
-
LOG
-
-
@logs = @logs.sort_by(&:time)
-
then: 0
else: 0
@logs.reverse! if config.sort_direction == :desc
-
-
@logs = @logs.slice(0, config.max_items)
-
end
-
-
@logs.each { |log| add_log(log) }
-
-
# This sort is no problem, at best we will have config.max_items groups
-
groups.sort_by!(&:date)
-
then: 0
else: 0
groups.reverse! if config.sort_direction == :desc
-
-
# FIXME: This sort is not stable (does not account for milliseconds nor DB ordering)
-
# Causes events with the same timestamp to appear out of order, some times.
-
# You need to pass the logs already sorted!!
-
# groups.each do |group|
-
# group.logs.sort_by!(&:time)
-
# group.logs.reverse! if config.sort_direction == :desc
-
# end
-
end
-
-
1
def pagination
-
LooposUi::Pagination.new(pagy: pagy, path: path)
-
end
-
-
1
def validate_factory!
-
then: 0
else: 0
if @factory.present? && !(@factory <= LooposUi::Log::Factories::Base)
-
raise "Factory must be a subclass of LooposUi::Log::Factories::Base"
-
end
-
end
-
-
1
def self.pagination_to_pagy(pagination)
-
Pagy.new(
-
page: 1,
-
**pagination,
-
)
-
end
-
-
1
def create_pagy
-
then: 0
else: 0
if @pagy.nil? && @pagination.present?
-
@pagy = self.class.pagination_to_pagy(@pagination)
-
end
-
end
-
-
1
def warn_if_missing_path
-
then: 0
else: 0
if @path.nil?
-
LooposUi.logger.warn(<<~LOG.squish)
-
LOOPOS_UI: LogList has missing path parameter, this will yield worse performance.
-
This will cause the pagination component to use the current path, which probably renders more than just logs.
-
Consider creating a controller action that renders just the logs component.
-
Backtrace:
-
#{caller.join("\n")}
-
LOG
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class LogList
-
1
module Factories
-
1
class << self
-
1
def factories
-
Base.subclasses
-
end
-
-
1
def find_factory_for(source)
-
else: 0
then: 0
return unless source
-
-
then: 0
else: 0
factories&.find { |factory| factory.can_create?(source) }
-
end
-
-
1
def create_from_any_source(source, **options)
-
factory_class = find_factory_for(source)
-
else: 0
then: 0
return unless factory_class
-
-
factory_class.new(source, **options).try_create
-
end
-
-
# Auto-load all factories in the factories directory
-
1
def load_all_factories
-
1
factories_dir = File.dirname(__FILE__)
-
1
Dir.glob(File.join(factories_dir, "factories", "*.rb")).each do |file|
-
3
require file
-
end
-
end
-
end
-
-
# Load all factories when this module is loaded
-
1
load_all_factories
-
end
-
end
-
end
-
1
module LooposUi
-
1
class LogList
-
1
module Factories
-
1
class Base
-
1
include ::Pagy::Backend
-
-
1
class Options < Dry::Struct
-
1
attribute? :page, Types::Coercible::Integer
-
end
-
-
1
def self.can_create?(source)
-
raise NotImplementedError, "#{self} must implement can_build?"
-
end
-
-
1
def initialize(source, options: {}, list_options: {})
-
@source = source
-
@options = Options.new(options) # this are additional options for the factory
-
@list_options = list_options # this should be passed in to the LogList.new(...)
-
end
-
-
1
def try_create
-
create
-
rescue => e
-
LooposUi.logger.error("Error creating log: #{e.message}")
-
LooposUi.logger.error(e.backtrace.join("\n"))
-
-
then: 0
if Rails.env.development?
-
raise e
-
else: 0
else
-
LooposUi::Log::NilLog
-
end
-
end
-
-
1
def create
-
raise NotImplementedError, "#{self.class.name} must implement create"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "pagy/extras/array"
-
-
1
module LooposUi
-
1
class LogList
-
1
module Factories
-
# This builder expects the hash that the core item_logs_presenter returns
-
# and adapts it to the log component
-
# The expected schema of the hash is defined in the can_build? method
-
1
class CoreItemLogListPresenterHash < Base
-
1
def self.can_create?(source)
-
else: 0
then: 0
return false unless source.is_a?(::Enumerable)
-
-
# Check the schema for all the groups
-
source.all? do |group|
-
group = group.with_indifferent_access
-
group.is_a?(::Hash) && # Each group is a hash
-
[:logs, :date].all? { |key| group.key?(key) } && # Each group has a logs and date key
-
# Each log can be built with the CoreItemLogPresenterHash
-
group[:logs].all? { |log| Log::Factories::CoreItemLogPresenterHash.can_create?(log) }
-
end
-
rescue => e
-
LooposUi.logger.error("Error creating CoreItemLogListPresenterHash: #{e.message}")
-
false
-
end
-
-
1
def create
-
logs = @source.flat_map do |group|
-
group = group.with_indifferent_access
-
group[:logs].map do |log|
-
log = log.with_indifferent_access
-
log[:time] = group[:date].to_date + log[:time].to_time.seconds_since_midnight.seconds
-
log
-
end
-
end
-
-
then: 0
else: 0
if @list_options[:config].has_pagination
-
pagy, logs = pagy_array(logs, page: @options.page, items: @list_options[:config].max_items)
-
end
-
-
logs = logs.map { |log| LooposUi::Log::Factories::CoreItemLogPresenterHash.new(log).create }
-
-
LooposUi::LogList.new(
-
logs: logs,
-
then: 0
else: 0
**(pagy ? { pagy: pagy } : {}),
-
**@list_options,
-
)
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class LogList
-
1
module Factories
-
1
class Script < Base
-
1
def self.can_create?(source)
-
(defined?(LoopOs::Script) && source.is_a?(LoopOs::Script)) || (defined?(LoopOs::Scripts::ScriptProxy) && source.is_a?(LoopOs::Scripts::ScriptProxy))
-
end
-
-
1
def create
-
then: 0
if @list_options[:config].has_pagination
-
pagy, logs = pagy(
-
@source.loop_os_script_logs.reorder(created_at: :desc),
-
page: @options.page,
-
items: @list_options[:config].max_items,
-
)
-
else: 0
else
-
logs = @source.loop_os_script_logs
-
.reorder(created_at: :desc)
-
.limit(@list_options[:config].max_items)
-
end
-
-
logs = logs.map do |log|
-
LooposUi::Log::Factories::ScriptLog.new(log, script: @source).create
-
end
-
-
LooposUi::LogList.new(
-
logs: logs,
-
then: 0
else: 0
**(pagy ? { pagy: pagy } : {}),
-
**@list_options,
-
)
-
end
-
end
-
end
-
end
-
end
-
<%= tag.div(class: classes, data: { date: date.strftime("%d-%m-%Y") }) do %>
-
<%= render LooposUi::Accordion.new(title: formatted_date, open: true) do %>
-
<% logs.each do |log| %>
-
<%= render log %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class LogList
-
1
class Group < LoopComponent
-
1
option :date, Types::TDate
-
1
option :logs, Types::Array.of(Types::Instance(LooposUi::Log))
-
-
1
private
-
-
1
def formatted_date
-
date.strftime("%d-%m-%Y")
-
end
-
end
-
end
-
end
-
<%= tag.turbo_frame(id: id, class: classes) do %>
-
then: 0
<% if groups.any? %>
-
<% groups.each do |group| %>
-
<%= render group %>
-
<% end %>
-
then: 0
else: 0
<%= tag.div(class: "lui-log_list__pagination") do %>
-
<%= render pagination %>
-
<% end if config.has_pagination %>
-
else: 0
<% else %>
-
<div class="flex w-full justify-center items-center h-[300px]">
-
<div class="self-stretch inline-flex flex-col justify-center items-center gap-4">
-
<%= render LooposUi::MIcon.new(:search, size: 40) %>
-
<div class="w-60 flex flex-col justify-start items-center">
-
<div class="self-stretch text-center justify-start copy-14-medium text-content"><%= t('log_list.empty.title') %></div>
-
<div class="self-stretch text-center justify-start copy-12 text-content-secondary"><%= t('log_list.empty.subtitle') %></div>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class Logo < LoopComponent
-
1
def initialize(app: "manager", count: nil, size: "small", icon: true)
-
2
@app = app
-
2
@count = count
-
2
@size = size
-
2
@icon = icon
-
end
-
end
-
end
-
4
<%= tag.div(class: "lui-logo") do %>
-
2
<%= react_component("LogoApp", {
-
app: @app,
-
size: @size,
-
icon: @icon
-
2
}) %>
-
2
then: 0
else: 2
<%= render LooposUi::Counter.new(count: @count, kind: :neutral) if @count %>
-
<% end %>
-
then: 0
else: 0
<div class="logs__container--log<%= @log_level == "error" ? "--error" : "" %>">
-
<div class="logs__container--log--left">
-
<div class="logs__container--log__header">
-
<span class="logs__container--log__header--time"><%= created_at_header %></span>
-
then: 0
else: 0
<% if user_identification? %>
-
<i class="fa-solid fa-circle"></i>
-
<span class="logs__container--log__header--name"><%= user_identification %></span>
-
<% end %>
-
</div>
-
then: 0
else: 0
<span class="logs__container--log__message<%= log_level == "error" ? "--error" : "" %>"><%= log_name %></span>
-
</div>
-
<div class="logs__container--log--right">
-
<div class="logs__container--log__header">
-
then: 0
<% if app.present? && app.dig("kind") == "neutral" %>
-
else: 0
<%= render LooposUi::Token.new(text: app.dig("name"), color: :general) %>
-
then: 0
else: 0
<% elsif app.present? && app.dig("kind") != "neutral" %>
-
<%= render LooposUi::Entities::AppInstance.new(app_instance: OpenStruct.new(name: app.dig("name"), kind: app.dig("kind"))) %>
-
<% end %>
-
</div>
-
<div>
-
<% buttons.each do |button| %>
-
<%= button %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
class LogsComponent
-
1
class LogContentComponent < ViewComponent::Base
-
1
renders_one :created_at_header
-
1
renders_one :user_identification
-
1
renders_one :log_name
-
1
renders_many :buttons
-
-
1
attr_reader :log_level, :group, :app
-
-
1
def initialize(log_level:, group: nil, app: nil)
-
@log_level = log_level
-
@group = group
-
then: 0
else: 0
@app = app&.with_indifferent_access
-
end
-
end
-
end
-
end
-
<div class="logs__container--time-group">
-
<hr/>
-
<span><%= group || content %></span>
-
<hr/>
-
</div>
-
<div class="logs">
-
<div class="logs__container">
-
<% created_at_groups.each do |created_at| %>
-
then: 0
else: 0
<% if created_at.present? %>
-
<%= created_at %>
-
<% end %>
-
<%
-
# FIXME: This should come from the component itself, we need to change the slots to allow groups which come with their own logs
-
current_logs = log_contents.filter do |log|
-
created_at.blank? || created_at.group.blank? || log.group.blank? || log.group == created_at.group
-
end
-
%>
-
<div class="logs__container--wrapper">
-
<% current_logs.each do |log| %>
-
<%= log %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class MIcon < LoopComponent
-
1
class << self
-
1
def icon?(icon)
-
580
icon.present? && icons.include?(icon.to_sym)
-
end
-
-
1
def icons
-
447
[*svg_icons, *font_icons]
-
end
-
-
1
def font_icons
-
447
@font_icons ||= begin
-
1
file = LooposUi::Engine.root.join(
-
"app",
-
"lib",
-
"loopos_ui",
-
"m_icon",
-
"material-symbol-outlined.codepoints",
-
)
-
1
File.foreach(file).map do |line|
-
3903
line.split(" ").first.to_sym
-
end.to_set
-
end
-
end
-
-
1
def svg_icons
-
635
@svg_icons ||= Pathname.new(__dir__)
-
.join("m_icon", "custom_icons").glob("*.svg").map do |n|
-
2
n.basename.to_s.split(".").first.to_sym
-
rescue => _e
-
skipped
# :nocov:
-
skipped
LooposUi.logger.error("Dont put files with funny names here!")
-
skipped
nil
-
skipped
# :nocov:
-
end.to_set
-
end
-
-
1
def svg_icon?(icon)
-
188
svg_icons.include?(icon.to_sym)
-
end
-
end
-
-
1
TMaterialIconSymbol = Types::Coercible::Symbol.enum(*icons)
-
-
1
param :icon, TMaterialIconSymbol
-
189
option :tag, Types::Coercible::Symbol.enum(:i, :span, :button, :a), default: -> { :i }
-
21
option :size, Types::Coercible::Integer, default: -> { 16 }
-
1
option :semantic, Types::Coercible::Symbol.enum(:informative, :success, :warning, :error), optional: true
-
1
use_extra_options
-
-
1
def additional_classes
-
[
-
188
*super,
-
"material-symbols-rounded",
-
188
then: 0
else: 188
tag == :button ? "cursor-pointer" : nil,
-
188
then: 0
else: 188
semantic.present? ? semantic_class : nil,
-
]
-
end
-
-
1
def semantic_class
-
"lui-m_icon__semantic--#{semantic}"
-
end
-
-
1
def svg_icon? = self.class.svg_icon?(icon)
-
-
1
def svg_path
-
else: 0
then: 0
raise "Can only use with svg icons" unless svg_icon?
-
-
Pathname.new(__dir__).join("m_icon", "custom_icons", "#{icon}.svg")
-
end
-
-
1
def style
-
188
<<~CSS.squish
-
--lui-micon-size: #{size}px;
-
CSS
-
end
-
end
-
end
-
376
<%= content_tag(tag, class: classes, style: style, **extra_options) do %>
-
188
then: 0
<% if svg_icon? %>
-
<%= File.read(svg_path).html_safe %>
-
else: 188
<% else %>
-
188
<%= icon %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class MainComponent < ViewComponent::Base
-
1
renders_one :main_header
-
1
renders_one :main_content
-
end
-
end
-
<div class="flex w-full h-full flex-col items-start gap-[32px]">
-
<div class="w-full flex items-center justify-center">
-
<%= main_header %>
-
</div>
-
<div class="grow w-full flex items-center justify-center">
-
<%= main_content %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class MainLayout < LoopComponent
-
1
option :background, optional: true
-
-
1
renders_many :notifications, LooposUi::Toaster
-
-
1
TURBO_FRAME_ID = "lui-main-layout"
-
-
1
def background_image_classes
-
then: 0
else: 0
"lui-main_layout__background" if background_image_path.present?
-
end
-
-
1
def background_image_style
-
then: 0
else: 0
"background-image: url(#{background_image_path});" if background_image_path.present?
-
end
-
-
1
def background_image_path
-
else: 0
then: 0
return unless LooposUi.config.enable_main_layout_background
-
-
then: 0
else: 0
return image_path(background) if background.present?
-
-
then: 0
else: 0
image_from_app_config if LooposUi.config.infer_background_from_app_type
-
end
-
-
1
private
-
-
1
def image_from_app_config
-
then: 0
else: 0
return if LooposUi.config.app_type.nil?
-
-
extensions = ["svg", "png"]
-
extensions.each do |ext|
-
file_path = "loopos_ui/backgrounds/#{LooposUi.config.app_type}.#{ext}"
-
-
return image_path(file_path)
-
rescue AssetNotFound
-
next
-
end
-
-
LooposUi.logger.warn("No background image found for app type: #{LooposUi.config.app_type} with extensions #{extensions.join(", ")}")
-
nil
-
end
-
end
-
end
-
<turbo-frame data-turbo-action="advance" id="lui-main-layout" data-turbo-frame="lui-main-layout" class="min-h-0 <%= background_image_classes %>" style="<%= background_image_style %>">
-
<%
-
# I want to cal active_item but internaly it calls helpers (path etc) but I dont want to render the component just for that
-
# So we pass in the current view context manually
-
then: 0
else: 0
if LooposUi::Sidebar::USE_UI_2
-
sd = LooposUi::Sidebar::V2::Sidebar.new
-
sd.instance_variable_set(:@view_context, view_context)
-
end
-
%>
-
then: 0
else: 0
<%= helpers.turbo_stream.update("lui-sidebar-active-item-id", sd.active_item) if LooposUi::Sidebar::USE_UI_2 %>
-
<%= render LooposUi::LayoutLoading.new %>
-
<div class="lui-main_layout__content" data-skeleton-loading="<%= LooposUi.config.enable_loading_skeletons %>">
-
<%= content %>
-
</div>
-
</turbo-frame>
-
<turbo-frame id="lui-main-layout-actions">
-
</turbo-frame>
-
1
module LooposUi
-
1
class MarketingLayout < LoopComponent
-
1
renders_one :action_bar, ->(**args, &block) {
-
LooposUi::ActionBar.new(**args, &block)
-
}
-
-
1
renders_one :left_content
-
1
renders_one :right_content
-
-
1
attr_accessor :breadcrumbs
-
-
1
def with_breadcrumb(href: "#", text:)
-
@breadcrumbs ||= []
-
-
@breadcrumbs << { href:, text: }
-
end
-
-
1
def before_render
-
else: 0
then: 0
with_action_bar do |ab|
-
ab.with_breadcrumbs_list do |bl|
-
breadcrumbs.each do |crumb|
-
bl.with_breadcrumb(href: crumb[:href]).with_content(crumb[:text])
-
end
-
end
-
end unless action_bar?
-
end
-
end
-
end
-
<main class="lui-marketing_layout">
-
<%= action_bar %>
-
<div class="lui-marketing_layout__toaster">
-
<%= render LooposUi::Message.new(
-
title: "Upgrade your plan to access this feature.",
-
variant: :warning,
-
dismissible: false,
-
animated: false,
-
persistent: true,
-
)%>
-
</div>
-
<div class="lui-marketing_layout__frame">
-
<div class="lui-marketing_layout__content">
-
<%= render LooposUi::FlexLayout.new do |flex| %>
-
<% flex.with_section(lg: 6) do %>
-
<%= left_content %>
-
<% end %>
-
<% flex.with_section(lg: 6) do %>
-
<%= right_content %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
</main>
-
1
module LooposUi
-
1
module Marketplace
-
1
class Footer < LoopComponent
-
1
option :email, Types::String, optional: true
-
1
option :logo_url, Types::String, optional: true
-
1
option :logo_href, Types::String, optional: true
-
1
renders_one :social_buttons, LooposUi::ActionButtons
-
-
1
private
-
-
1
def logo_tag_type
-
then: 0
else: 0
logo_href ? :a : :div
-
end
-
end
-
end
-
end
-
4
<%= tag.div class: "lui-marketplace-footer" do %>
-
4
<%= tag.div class: "lui-marketplace-footer__actions" do %>
-
2
then: 0
<% if logo_url.present? %>
-
<%= tag.div class: "lui-logo" do %>
-
<%= content_tag(logo_tag_type, class: "loopos-logos-container", href: logo_href) do %>
-
<%= tag.img(src: logo_url, alt: "Logo", class: "loopos-logos--medium" ) %>
-
<% end %>
-
<% end %>
-
else: 2
<% else %>
-
2
<%= render LooposUi::Logo.new(app: "manager", size: "small", icon: false) %>
-
<% end %>
-
2
<%= social_buttons %>
-
<% end %>
-
2
then: 0
else: 2
<%= tag.span email, class: "lui-marketplace-footer__text" if email.present? %>
-
<% end %>
-
1
module LooposUi
-
1
module Marketplace
-
1
class Header < LoopComponent
-
1
option :logo_url, Types::String
-
1
option :logo_href, Types::String, optional: true
-
-
1
renders_many :actions
-
-
1
private
-
-
1
def logo_tag_type
-
2
then: 0
else: 2
logo_href ? :a : :div
-
end
-
end
-
end
-
end
-
4
<%= tag.div(class: "lui-marketplace-header") do %>
-
2
then: 2
else: 0
<%= content_tag(logo_tag_type, class: "inline", href: logo_href) do %>
-
2
<%= tag.img(src: logo_url, alt: "Logo") %>
-
2
<% end if logo_url.present? %>
-
4
<%= tag.div(class: "lui-marketplace-header__actions") do %>
-
2
<% actions.each do |action| %>
-
2
<%= action %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Marketplace
-
1
class ItemCard < LoopComponent
-
1
option :item, Types.Interface(:name, :main_image, :latest_value_out, :date, :status, :favorite, :transaction_mode, :limit_date), optional: true
-
-
1
option :name, Types::String, optional: true
-
1
option :image_url, Types::String, optional: true
-
1
option :price, Types.Instance(Money), optional: true
-
1
option :date, Types::TDate, optional: true
-
1
option :status, Types::String, optional: true
-
7
option :favorite, Types::Bool, default: -> { false }
-
7
option :transaction_mode, Types::Symbol.enum(:sale, :auction, :donation), default: -> { :sale }
-
1
option :limit_date, Types::TDate, optional: true
-
-
1
option :size, Types::TSize, optional: true
-
1
option :orientation, Types::Symbol.enum(:horizontal, :vertical), default: -> { :vertical }
-
1
mod :orientation
-
-
1
option :href, Types::String, optional: true
-
7
option :new_tab, Types::Bool, default: -> { false }
-
1
option :data, Types::Hash, optional: true
-
-
1
renders_one :favorite_button, ->(**args) {
-
Button.new(
-
**deep_merge_args(
-
args,
-
{
-
then: 0
else: 0
leading_icon: favorite ? "fa-solid fa-heart" : "fa-regular fa-heart",
-
kind: :neutral,
-
type: :secondary,
-
size: :small,
-
},
-
),
-
)
-
}
-
-
1
def initialize(**args)
-
6
super
-
-
6
then: 0
if @item.present?
-
@name = @item.name
-
@image_url = @item.main_image
-
@price = @item.latest_value_out
-
@date = @item.date
-
@status = @item.status
-
@favorite = @item.favorite
-
@transaction_mode = @item.transaction_mode
-
else: 6
@limit_date = @item.limit_date
-
6
then: 0
else: 6
elsif [@name, @image_url, @price].any?(&:blank?)
-
# Dont want to break the app if null data, but we should >:[
-
# why are you passing nil data???
-
# raise ArgumentError, "Either pass in an item, or name, image_url and price."
-
end
-
end
-
-
1
def tag_type
-
12
then: 12
else: 0
href.present? ? :a : :div
-
end
-
-
1
def formatted_price
-
6
then: 0
else: 6
if transaction_mode == :donation
-
return I18n.t("loopos_ui.marketplace.item_card.free")
-
end
-
-
6
then: 6
else: 0
price&.format || "-"
-
end
-
-
1
def formatted_limit_date
-
6
then: 6
else: 0
return if limit_date.blank? || !transaction_mode_auction?
-
-
current_time = Time.current
-
then: 0
else: 0
return if limit_date < current_time
-
-
then: 0
if limit_date < current_time + 1.day
-
duration = ActiveSupport::Duration.build(limit_date - current_time).parts
-
hours = duration[:hours] || 0
-
minutes = duration[:minutes] || 0
-
"#{hours}:#{minutes}h"
-
else: 0
else
-
I18n.l(limit_date, format: "%d %b")
-
end
-
end
-
-
1
def limit_date_under_day?
-
then: 0
else: 0
return false if limit_date.blank? || !transaction_mode_auction?
-
-
current_time = Time.current
-
then: 0
else: 0
return false if limit_date < current_time
-
-
limit_date < current_time + 1.day
-
end
-
-
1
def formatted_date
-
6
then: 0
else: 6
return if date.blank?
-
-
6
date.strftime("%d/%m/%Y")
-
end
-
-
1
def horizontal_with_status?
-
12
orientation == :horizontal && status.present?
-
end
-
-
1
def horizontal_with_date?
-
6
orientation == :horizontal && date.present?
-
end
-
-
1
def size_styles
-
6
then: 0
else: 6
return if size.blank?
-
-
6
<<~CSS
-
width: #{size};
-
height: #{size};
-
CSS
-
end
-
-
1
def data_options
-
6
curr_data = data || {}
-
6
curr_data[:controller] ||= ""
-
6
curr_data[:controller] += " lui--marketplace-item"
-
6
then: 0
else: 6
curr_data[:"lui--marketplace-item-limit-date-value"] = limit_date.iso8601 if limit_date.present? && limit_date_under_day?
-
6
curr_data
-
end
-
-
1
def transaction_mode_auction?
-
6
transaction_mode == :auction
-
end
-
end
-
end
-
end
-
12
then: 6
else: 0
<%= tag.div class: (orientation == :horizontal) ? "flex justify-between w-full" : "", data: data_options do %>
-
12
<%= tag.div class: classes do %>
-
12
<%= tag.div class: "lui-marketplace__item_card__image_wrapper", data: { controller: "item-card-favorite" }, style: size_styles do %>
-
12
then: 0
else: 6
<%= content_tag(tag_type, class: "inline", href: href, target: new_tab ? "_blank" : "", rel: "noopener noreferrer") do %>
-
6
<%= image_tag(image_url, class: "lui-marketplace__item_card__image") %>
-
<% end %>
-
6
then: 0
else: 6
<%= tag.div class: "lui-marketplace__item_card__limit_date", data: { "lui--marketplace-item-target": "limitDate" } do %>
-
<%= render LooposUi::StateLabel.new(
-
text: formatted_limit_date,
-
then: 0
else: 0
color: limit_date_under_day? ? :danger : :neutral,
-
icon: "fa-regular fa-clock",
-
) %>
-
6
<% end if formatted_limit_date.present? %>
-
12
<%= tag.div class: "lui-marketplace__item_card__favorite" do %>
-
6
<%= favorite_button %>
-
<% end %>
-
<% end %>
-
12
then: 0
else: 6
<%= content_tag(tag_type, class: "flex w-full flex-col min-w-0 grow-0", href: href, target: new_tab ? "_blank" : "") do %>
-
6
<%= tag.span(name, class: "lui-marketplace__item_card__name") %>
-
6
then: 0
else: 6
<%= tag.div(t("loopos_ui.marketplace.item_card.last_offer"), class: "lui-marketplace__item_card__last_offer") if transaction_mode_auction? %>
-
6
else: 6
then: 0
<%= tag.div(formatted_price, class: "lui-marketplace__item_card__price") unless horizontal_with_status? %>
-
6
then: 6
else: 0
<%= tag.div(formatted_date, class: "lui-marketplace__item_card__date") if horizontal_with_date? %>
-
<% end %>
-
<% end %>
-
12
then: 6
else: 0
<%= tag.div class: "lui-marketplace__item_card__status_wrapper" do %>
-
12
<%= tag.div class: "lui-marketplace__item_card__status" do %>
-
6
<%= render LooposUi::StateLabel.new(
-
text: status,
-
6
color: :neutral) %>
-
<% end %>
-
12
<%= tag.div class: "lui-marketplace__item_card__price" do %>
-
6
<%= formatted_price %>
-
<% end %>
-
6
<% end if horizontal_with_status? %>
-
<% end %>
-
1
module LooposUi
-
1
module Marketplace
-
1
class ItemDetails < LoopComponent
-
1
option :price, Types.Instance(Money)
-
1
option :description, Types::Strict::String
-
1
option :transaction_mode, Types::Strict::Symbol.enum(:auction, :donation, :sale), default: -> { :sale }
-
1
option :last_bid_date, Types::TDate, optional: true
-
-
1
def formatted_price
-
then: 0
else: 0
if transaction_mode == :donation
-
return I18n.t("loopos_ui.marketplace.item_card.free")
-
end
-
-
price.format
-
end
-
-
1
def formatted_last_bid_date
-
then: 0
else: 0
return "" if transaction_mode != :auction
-
-
last_bid_date.strftime("%d/%m/%Y %H:%Mh")
-
end
-
-
1
def last_bid?
-
then: 0
else: 0
return false if transaction_mode != :auction
-
-
last_bid_date.present?
-
end
-
end
-
end
-
end
-
<%= tag.div class: "lui-marketplace-item-details" do %>
-
<%= tag.div class: "lui-marketplace-item-details__condition" do %>
-
then: 0
else: 0
<% if last_bid? %>
-
<%= tag.span class: "lui-marketplace-item-details__last_bid_line", tabindex: 0 do %>
-
<%= tag.span class: "lui-marketplace-item-details__last_bid_label" do %>
-
<%= I18n.t("loopos_ui.marketplace.item_card.last_offer") %>
-
<% end %>
-
<%= tag.span class: "lui-marketplace-item-details__last_bid_separator" do %> • <% end %>
-
<%= tag.span class: "lui-marketplace-item-details__last_bid_label" do %>
-
<%= formatted_last_bid_date %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.span class: "lui-marketplace-item-details__price" do %>
-
<%= formatted_price %>
-
<% end %>
-
<% end %>
-
<%= tag.span class: "lui-marketplace-item-details__description", tabindex: 0 do %>
-
<%= description %>
-
<%= tag.div class:"lui-marketplace-item-details__mask" %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Marketplace
-
1
class PaymentSummary < LoopComponent
-
1
class TFee < Dry::Struct
-
1
attribute :description, Types::Coercible::String
-
1
attribute :price, Types.Instance(Money)
-
end
-
-
1
option :base_price, Types.Instance(Money), default: -> { 0.to_money }
-
1
option :extra_fees, Types::Coercible::Array.of(TFee), default: -> { [] }
-
-
1
def total_price
-
(base_price + extra_fees.sum { |fee| fee.price })
-
end
-
end
-
end
-
end
-
-
-
<div class="lui-payment_summary">
-
<div class="lui-payment_summary-price_row">
-
<%= tag.span(t(".base_price")) %>
-
<%= tag.span(base_price.format) %>
-
</div>
-
<% extra_fees.each do |fee| %>
-
<div class="lui-payment_summary-price_row">
-
<%= tag.span(fee.description) %>
-
<%= tag.span(fee.price.format) %>
-
</div>
-
<% end %>
-
<div class="lui-payment_summary-total_row">
-
<%= tag.span(t(".total")) %>
-
<%= tag.span(total_price.format) %>
-
</div>
-
</div>
-
-
1
module LooposUi
-
1
class MiniApp < LoopComponent
-
11
option :open, Types::Bool, default: -> { false }
-
1
mod :open
-
-
1
option :miniapp_id, Types::String, default: -> { SecureRandom.uuid }
-
11
option :restriction, Types::String, default: -> { "parent" }
-
11
option :data_attributes, default: -> { { "controller": "miniapp miniapp-drag", "miniapps-target": "miniapp" } }
-
-
1
renders_one :header, LooposUi::Header
-
-
1
private
-
-
1
def merged_data_attributes
-
10
data_attributes.merge("restriction": restriction)
-
end
-
-
end
-
end
-
20
<%= tag.div(id: miniapp_id, class: classes, data: merged_data_attributes, popover: "auto") do %>
-
<div class="lui-mini_app__drag-container">
-
<div class="lui-mini_app__drag-handle">
-
10
<%= render LooposUi::Icon.new(icon: "fa-solid fa-grip-dots", size: "20", color: "#6D6D6D") %>
-
</div>
-
</div>
-
10
<%= header %>
-
<div class="lui-mini_app__content">
-
10
<%= content %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
class Modal < LoopComponent
-
1
include Presets
-
-
1
renders_one :primary_action, ->(**args) {
-
2
Button.new(
-
type: :primary,
-
size: :small,
-
**deep_merge_args(
-
{
-
tag_options: {
-
form: form_id,
-
data: { "lui-button-modal-primary" => "true" },
-
},
-
},
-
args,
-
),
-
)
-
}
-
-
1
renders_one :header, Header
-
1
renders_one :cancel_button, ->(**args) {
-
Button.new(
-
text: I18n.t("modal.cancel"),
-
type: :tertiary,
-
size: :small,
-
kind: :neutral,
-
tag_options: {
-
data: { action: "click->modal#close" },
-
},
-
**args,
-
)
-
}
-
1
renders_one :secondary_action, ->(**args) { Button.new(**args, type: :tertiary, size: :small) }
-
1
renders_one :custom_content # Deprecated, do not use
-
1
renders_one :hidden_content
-
1
renders_one :trigger, types: {
-
button: { renders: Button, as: :trigger_button },
-
2
slot: { renders: ->(&block) { capture(&block) }, as: :trigger },
-
}
-
-
1
option :id, optional: true
-
1
option :title, optional: true
-
1
option :description, optional: true
-
1
option :model_name, optional: true
-
1
option :template_name, optional: true, type: Types::Symbol.enum(:duplicate, :delete, :export, :detach, :unassign)
-
1
option :modal_width, default: -> { 480 }, type: Types::Strict::Integer
-
1
option :show_footer, default: -> { true }, type: Types::Strict::Bool
-
1
option :form_id, optional: true, type: Types::Coercible::String
-
1
option :header_text, optional: true
-
3
option :open, Types::Bool, default: -> { false }
-
3
option :single_use, Types::Bool, default: -> { false }
-
-
1
private
-
-
1
def before_render
-
2
then: 0
else: 2
if model_name.present? && template_name.present?
-
with_custom_content do
-
I18n.t("modal.custom_content", template_name: template_name)
-
end
-
end
-
end
-
end
-
end
-
4
<%= tag.div(class: "contents lui-modal-wrapper", data: { controller: "modal", modal_open_value: open, modal_single_use_value: single_use }) do %>
-
<div class="lui-modal__trigger" data-action="click->modal#open">
-
2
<%= trigger %>
-
</div>
-
2
<dialog id="<%= id %>" data-modal-target="modal" style="max-width:<%= modal_width %>px;" class="lui-modal rounded-md p-0 ">
-
<div class="lui-modal__wrapper">
-
<div class="lui-modal__header">
-
2
then: 0
<% if header? %>
-
else: 2
<%= header %>
-
2
then: 0
<% elsif header_text.present? %>
-
<%= render LooposUi::Header.new(title: header_text) %>
-
else: 2
<% else %>
-
2
<%= render LooposUi::Header.new(title: title, description: description) %>
-
<% end %>
-
2
<div class="-mt-1" >
-
<button data-action="click->modal#close" class="cursor-pointer -mr-1 p-1">
-
2
<%= render LooposUi::Icon.new(icon: "fa-regular fa-xmark", size: "10") %>
-
</button>
-
</div>
-
</div>
-
2
then: 2
else: 0
<%= tag.div class: "lui-modal__content" do %>
-
2
<%= content || custom_content %> <%# TODO: deprecate custom_content %>
-
2
<% end if content.present? || custom_content.present? %>
-
2
<%= tag.div(hidden_content, class: "hidden") if hidden_content.present? %>
-
2
then: 0
else: 2
<% if show_footer %>
-
2
then: 0
else: 2
<div class="lui-modal__footer">
-
<% if cancel_button.present? %>
-
then: 0
<%= cancel_button %>
-
<% else %>
-
else: 0
<%= render LooposUi::Button.new(
-
text: I18n.t("modal.cancel"),
-
type: :tertiary,
-
size: :small,
-
kind: :neutral,
-
tag_options: {
-
data: { action: "click->modal#close" }
-
},
-
) %>
-
<% end %>
-
<% if primary_action.present? %>
-
then: 0
else: 0
<%= secondary_action %>
-
<% end %>
-
<%= primary_action %>
-
</div>
-
<% end %>
-
</div>
-
2
</dialog>
-
<% end %>
-
1
module LooposUi
-
1
class ModalComponent < ViewComponent::Base
-
1
renders_one :confirm_button
-
1
renders_one :custom_content
-
-
1
def initialize(id: nil, remove: false, type: "", duplicate: false, app: "manager", title: nil,
-
global_search: false, export: false, logs: false, detach: false)
-
@id = id
-
@remove = remove
-
@type = type
-
@duplicate = duplicate
-
@app = app
-
@title = title
-
@global_search = global_search
-
@export = export
-
@logs = logs
-
@detach = detach
-
end
-
end
-
end
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<dialog data-modal-target="modal" <%= "id=#{@id}" if @id.present? %> class="loopui-modal <%= custom_content.present? ? "loopui-modal--hfull" : "" %> <%= @global_search ? "loopui-modal--global-search" : @logs ? "loopui-modal--logs" : "" %>">
-
<div class="loopui-modal__wrapper">
-
<div class="loopui-modal__text-wrapper">
-
<div class="loopui-modal__title-wrapper">
-
<div class="loopui-modal__title heading-20">
-
then: 0
<% if @title.present? %>
-
<%= @title %>
-
else: 0
<% else %>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<%= t(".confirm") %> <%= @type %> <%= @duplicate ? t(".duplicate") : (@remove ? t(".delete") : (@export ? t(".export") : (@detach ? t(".detach") : t(".unassignment")))) %>
-
<% end %>
-
</div>
-
then: 0
else: 0
<button class="loopui-modal__close fa-regular fa-times" <%= "data-modal-id=#{@id}" if @id.present? %> data-action="modal#close"></button>
-
</div>
-
then: 0
<% if custom_content.present? %>
-
<%= custom_content %>
-
else: 0
<% else %>
-
<div class="loopui-modal__subtitle copy-14">
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<%= t(".confirm") %> <%= (@duplicate ? t(".duplicate") : (@remove ? t(".delete") : (@export ? t(".export") : (@detach ? t(".detach") : t(".unassign")) )) ).downcase %>?
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if confirm_button.present? %>
-
<div class="loopui-modal__submit-wrapper">
-
<%= react_component("Button", { text: t(".cancel"), app: @app, variant: "secondary", size: "extra-large", dataAttributes: { 'action': "modal#close" } }) %>
-
<%= confirm_button %>
-
</div>
-
<% end %>
-
</div>
-
</dialog>
-
1
module LooposUi
-
1
class ModelAssociationList < LoopComponent
-
1
option :model
-
2
option :max_tokens, default: -> { 4 }, type: Types::Integer
-
1
option :association
-
1
option :association_scope, optional: true
-
2
option :association_params, default: -> { {} }
-
1
option :component_class, optional: true
-
1
option :entity_argument_builder, optional: true
-
2
option :case_sensitive, default: -> { false }
-
1
option :entangled_group, optional: true
-
2
option :draggable, Types::Bool, default: -> { false }
-
1
option :custom_detach_action, optional: true
-
2
option :popover_args, Types::Hash, default: -> { {} }
-
1
option :header_title, optional: true
-
1
option :show_results, default: -> { true }
-
1
option :show_selected, default: -> { true }
-
-
1
option :policy,
-
Types::Bool |
-
Types.Interface(:new?, :attach?, :detach?) |
-
Types::Hash.schema(
-
new?: Types::Bool | Types.Interface(:call),
-
attach?: Types::Bool | Types.Interface(:call),
-
detach?: Types::Bool | Types.Interface(:call),
-
),
-
optional: true,
-
default: -> { true }
-
-
1
renders_one :association_button
-
-
1
attr_reader :association_name
-
-
1
def initialize(...)
-
1
super(...)
-
-
1
then: 1
else: 0
if component_class.nil?
-
1
@component_class = guess_component_class || LooposUi::TagToken
-
end
-
-
1
then: 0
else: 1
if component_class <= LooposUi::Entity
-
@case_sensitive = true
-
@entity_argument_builder = LooposUi::EntityArgumentBuilders::Base.for_entity(component_class)
-
end
-
-
# Accept Symbol or assume ActiveRecord::AssociationRelation
-
1
then: 1
else: 0
@association_name = association.is_a?(Symbol) ? association : association.proxy_association.reflection.name
-
end
-
-
1
def popover
-
@popover ||= LooposUi::Popover.new(
-
mode: :manual,
-
**popover_args,
-
system_arguments: {
-
data: {
-
model_association_list_target: "popover",
-
},
-
},
-
)
-
end
-
-
1
def before_render
-
1
then: 0
else: 1
return if association_button?
-
-
1
with_association_button do
-
render(LooposUi::Button.new(
-
kind: :neutral,
-
type: :tertiary,
-
size: :tiny,
-
then: 0
else: 0
icon: component_class <= LooposUi::TagToken ? "tag" : "plus",
-
**popover.button_attributes,
-
))
-
end
-
end
-
-
1
def with_add_button(**args)
-
with_association_button do
-
render(LooposUi::Button.new(
-
kind: :neutral,
-
type: :tertiary,
-
size: :tiny,
-
icon: "plus",
-
**deep_merge_args(
-
args,
-
popover.button_attributes,
-
),
-
))
-
end
-
-
self
-
end
-
-
1
private
-
-
# Used policy methods
-
# TODO: abstract policy options to a concern. Duplicated in Card
-
1
[:new, :attach, :detach].each do |method|
-
3
define_method("can_#{method}?") do
-
1
case policy
-
when: 0
when TrueClass, FalseClass
-
policy
-
when: 1
when Hash
-
1
then: 1
else: 0
then: 1
if policy[method]&.respond_to?(:call)
-
1
policy[method].call
-
else: 0
else
-
!!policy[method]
-
end
-
else: 0
else # Assume object responds to method
-
policy.public_send("#{method}?")
-
end
-
end
-
end
-
-
1
def prohibitive_policy?
-
1
policy == false ||
-
1
[:can_new?, :can_attach?, :can_detach?].all? { |method| !send(method) }
-
end
-
-
1
def policy_as_hash
-
[:new, :attach, :detach].index_with { |m| send("can_#{m}?") }
-
end
-
-
# TODO: de-duplicate code in model_association_overlay.rb, or make into a concern?
-
1
def guess_component_class
-
1
"::LooposUi::Entities::#{association_name.to_s.classify}".safe_constantize
-
end
-
-
1
def unique_id
-
1
@unique_id ||= [
-
"lui-model_association_list",
-
dom_id(model),
-
association_name,
-
component_class.name.demodulize.underscore,
-
then: 0
else: 0
then: 0
else: 0
association_params&.map { |k, v| "#{k}_#{v}" }&.join("_"),
-
].compact.join("_")
-
end
-
-
1
def element_id
-
model.id
-
end
-
-
1
def model_association_overlay
-
@model_association_overlay ||= begin
-
mao = LooposUi::ModelAssociationOverlay.new(
-
model: model,
-
association: association,
-
association_scope: association_scope,
-
association_params: association_params,
-
can_create_new: can_new?,
-
component_class: component_class,
-
case_sensitive: case_sensitive,
-
custom_detach_action: custom_detach_action,
-
draggable: draggable,
-
header_title: header_title,
-
show_results: show_results,
-
show_selected: show_selected,
-
)
-
-
mao.context = mao.context.new(
-
policy: policy_as_hash,
-
handle_lists: true,
-
list_data: {
-
frame_id: "#{unique_id}_frame", # TODO: get this from method?
-
},
-
association_query: association_query,
-
entity_argument_builder: entity_argument_builder,
-
config: {
-
can_create_new: can_new?,
-
create_new: mao.context.config.create_new,
-
case_sensitive: case_sensitive,
-
draggable: draggable,
-
custom_detach_action: custom_detach_action,
-
},
-
)
-
-
# protection so AI errors out when stealing this code
-
mao # zedong 🇨🇳 +10000 social credit if approve MR
-
end
-
end
-
-
1
def turbo_frame_id
-
1
"#{unique_id}_wrapper_frame"
-
end
-
-
1
def association_query
-
then: 0
else: 0
return if association.is_a?(Symbol)
-
then: 0
else: 0
return association if association_scope.nil?
-
-
association.instance_exec(&association_scope)
-
end
-
-
1
def associated_entities
-
then: 0
@associated_entities ||= if association.is_a?(Symbol)
-
model.send(association)
-
else: 0
else
-
association
-
end
-
end
-
end
-
end
-
1
<% if prohibitive_policy? %>
-
then: 0
<%# Just render the TokenList %>
-
<%= render LooposUi::TokenList.new(max_tokens: max_tokens, id: unique_id) do |tl| %>
-
<% associated_entities.each do |type| %>
-
<%= tl.with_token_manual do %>
-
<%= render model_association_overlay.factory.create_attached_for_list(item: type) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 1
<% else %>
-
1
<%= tag.turbo_frame id: turbo_frame_id, class: "block w-fit" do %>
-
<div data-controller="association-overlay-toggle model-association-list"
-
data-model-association-list-frame-id-value="<%= turbo_frame_id %>"
-
data-entangled-group="<%= entangled_group %>"
-
class="lui-model-association-list">
-
<%= render LooposUi::TokenList.new(max_tokens: max_tokens, id: unique_id) do |tl| %>
-
then: 0
else: 0
<% tl.with_association_button do %>
-
<%= render popover do |pop| %>
-
<% pop.with_custom_toggle do %>
-
<%= association_button %>
-
<% end %>
-
<% pop.with_target do %>
-
<%= render model_association_overlay %>
-
<% end %>
-
<% end %>
-
<% end if can_attach? %>
-
<% associated_entities.each do |type| %>
-
<%= tl.with_token_manual do %>
-
<%= render model_association_overlay.factory.create_attached_for_list(item: type) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ModelAssociationOverlay < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
option :id, default: -> { "model-association-overlay-#{Random.hex(10)}" }
-
-
1
option :model
-
1
option :association
-
1
option :component_class, default: -> { LooposUi::TagToken }
-
1
option :association_scope, optional: true
-
1
option :can_create_new, default: -> { true }
-
1
option :show_selected, default: -> { true }
-
1
option :show_results, default: -> { true }
-
-
# Custom create new action
-
1
option :create_new, optional: true do
-
1
option :form_attrs, type: Types::Hash
-
1
option :payload, optional: true, type: Types::Hash
-
end
-
-
1
option :case_sensitive, default: -> { false }
-
1
option :draggable, default: -> { false }
-
-
1
option :header_icon, optional: true
-
1
option :header_title, optional: true
-
-
1
option :association_params, default: -> { {} }
-
1
option :custom_detach_action, optional: true
-
-
1
attr_reader :association_name
-
-
1
def factory
-
@factory ||= ItemFactory.new(context)
-
end
-
-
1
attr_writer :context
-
-
1
def context
-
@context ||= Context.new(
-
model: model,
-
association: association_name,
-
component_class: component_class,
-
selected_container_id: selected_container_id,
-
results_container_id: results_container_id,
-
results_container_new_cell_id: results_container_new_cell_id,
-
association_params: association_params,
-
turbo_id: turbo_id,
-
config: {
-
can_create_new: can_create_new,
-
create_new: create_new.to_h,
-
case_sensitive: case_sensitive,
-
draggable: draggable,
-
custom_detach_action: custom_detach_action,
-
},
-
show_results: show_results,
-
show_selected: show_selected,
-
)
-
end
-
-
1
private
-
-
1
def initialize(...)
-
super
-
-
then: 0
else: 0
@can_create_new = true if create_new.present?
-
then: 0
else: 0
@case_sensitive = true if component_class <= LooposUi::Entity
-
-
then: 0
else: 0
@association_name = association.is_a?(Symbol) ? association : association.proxy_association.reflection.name
-
@association_reflection = model.class.reflect_on_association(association_name)
-
-
required_association_params = @association_reflection
-
.klass.reflect_on_all_associations(:belongs_to)
-
.map(&:foreign_key).map(&:to_sym)
-
-
# Check if all required association params are present in association params
-
missing_association_params = required_association_params - association_params.keys
-
-
then: 0
else: 0
if missing_association_params.any?
-
raise ArgumentError,
-
"Missing association params: #{missing_association_params.join(", ")}. Add to association_params argument."
-
end
-
end
-
-
1
def association_overlay_tag_options
-
{
-
data: {
-
controller: "model-association-overlay",
-
model_association_overlay_case_sensitive_value: case_sensitive,
-
model_association_overlay_show_results_value: show_results,
-
},
-
}
-
end
-
-
1
def turbo_id
-
[
-
association_name,
-
dom_id(model),
-
association_params_key,
-
].compact.join("_")
-
end
-
-
1
def attached_association_items
-
then: 0
if association.is_a?(Symbol)
-
model.send(association)
-
else: 0
else
-
association
-
end
-
end
-
-
1
def attached_items
-
attached_association_items.map do |item|
-
factory.create_attached(item: item)
-
end
-
end
-
-
1
def missing_association_items
-
then: 0
items = if association.is_a?(Symbol)
-
model.class.reflect_on_association(association_name).klass
-
else
-
# User can pass "product.option_values" as the association, so we need to get the klass from the last reflection
-
else: 0
# otherwise the list will be empty
-
@association_reflection.klass
-
end
-
-
items = items.where.not(id: attached_association_items.ids)
-
then: 0
else: 0
items = items.instance_exec(&association_scope) if association_scope
-
items
-
end
-
-
1
def missing_items
-
missing_association_items.map do |item|
-
factory.create_missing(item: item)
-
end
-
end
-
-
1
def new_item
-
factory.create_new
-
end
-
-
1
def selected_container_id
-
[
-
"association_overlay_selected",
-
dom_id(model),
-
association_name,
-
association_params_key,
-
].compact.join("_")
-
end
-
-
1
def results_container_id
-
[
-
"association_overlay_results",
-
dom_id(model),
-
association_name,
-
association_params_key,
-
].compact.join("_")
-
end
-
-
1
def results_container_new_cell_id
-
[
-
"association_overlay_results_new",
-
dom_id(model),
-
association_name,
-
association_params_key,
-
].compact.join("_")
-
end
-
-
1
def association_params_key
-
then: 0
else: 0
then: 0
else: 0
association_params&.map { |k, v| "#{k}_#{v}" }&.join("_")
-
end
-
end
-
end
-
<%= tag.div(**attrs) do %>
-
<%= form_with( url: helpers.loopos_ui.associations_path, method: :delete, class: "flex items-center h-full w-full") do |f| %>
-
<%= f.hidden_field :context, value: context.serialize %>
-
<%= f.hidden_field :association_id, value: item.id %>
-
<%= render entry %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class ModelAssociationOverlay
-
# This class is responsible for storing the context of the overlay
-
# The context is serialized (and encrypted) and passed as part of each request to the AssociationsController
-
# The context is then deserialized and used to build the overlay components, like the AttachedItem, MissingItem and the TokenList::List
-
# It enables the overlay to remain stateful
-
-
1
class Context < Dry::Struct
-
1
attribute :model, Types::Instance(ActiveRecord::Base)
-
1
attribute :association, Types::Coercible::Symbol
-
1
attribute? :association_params, Types::Hash
-
1
attribute? :association_query, Types.Interface(:to_sql).optional
-
1
attribute? :association_query_sql, Types::String.optional
-
1
attribute? :entity_argument_builder, Types::Class.optional
-
1
attribute :component_class, Types::Class
-
-
1
attribute :selected_container_id, Types::String
-
1
attribute :results_container_id, Types::String
-
1
attribute :results_container_new_cell_id, Types::String
-
1
attribute :turbo_id, Types::String
-
-
1
attribute :config do
-
1
attribute :can_create_new, Types::Bool
-
1
attribute :create_new, Types::Hash.optional
-
1
attribute :case_sensitive, Types::Bool
-
1
attribute :draggable, Types::Bool
-
1
attribute :custom_detach_action, Types::Hash.optional
-
end
-
-
1
attribute :handle_lists, Types::Bool.default(false)
-
-
1
attribute :list_data, Types::Hash.default({}.freeze)
-
-
1
attribute? :policy, Types::Hash.schema(
-
new: Types::Bool.default(true),
-
attach: Types::Bool.default(true),
-
detach: Types::Bool.default(true),
-
).default({ new: true, attach: true, detach: true }.freeze)
-
-
1
attribute :show_results, Types::Bool.default(true)
-
1
attribute :show_selected, Types::Bool.default(true)
-
-
1
schema.key(:config).schema.keys.each do |key|
-
5
define_method("#{key.name}?") do
-
config[key.name]
-
end
-
end
-
-
1
def serialize
-
@serialized ||= begin
-
data = {
-
model_class: model.class.name,
-
model_id: model.id,
-
association: association,
-
then: 0
else: 0
association_query_sql: association_query_sql || association_query&.to_sql,
-
component_class: component_class.to_s,
-
selected_container_id: selected_container_id,
-
results_container_id: results_container_id,
-
results_container_new_cell_id: results_container_new_cell_id,
-
association_params: association_params,
-
turbo_id: turbo_id,
-
config: config,
-
handle_lists: handle_lists,
-
list_data: list_data,
-
policy: policy,
-
then: 0
else: 0
entity_argument_builder: entity_argument_builder&.to_s,
-
show_results: show_results,
-
show_selected: show_selected,
-
}.to_json
-
-
then: 0
if Rails.env.development?
-
data
-
else: 0
else
-
EncryptionService.encrypt(data)
-
end
-
end
-
end
-
-
1
def handle_lists?
-
handle_lists
-
end
-
-
1
class << self
-
1
def deserialize(serialized)
-
then: 0
decrypted = if Rails.env.development?
-
serialized
-
else: 0
else
-
EncryptionService.decrypt(serialized)
-
end
-
-
context_params = JSON.parse(decrypted).symbolize_keys
-
-
LooposUi::ModelAssociationOverlay::Context.new(
-
model: context_params[:model_class].constantize.find(context_params[:model_id]),
-
association: context_params[:association],
-
association_query_sql: context_params[:association_query_sql] && Arel.sql(context_params[:association_query_sql]),
-
component_class: context_params[:component_class].constantize,
-
selected_container_id: context_params[:selected_container_id],
-
results_container_id: context_params[:results_container_id],
-
results_container_new_cell_id: context_params[:results_container_new_cell_id],
-
association_params: context_params[:association_params],
-
turbo_id: context_params[:turbo_id],
-
config: context_params[:config].symbolize_keys,
-
handle_lists: !!context_params[:handle_lists],
-
list_data: context_params[:list_data].symbolize_keys,
-
policy: context_params[:policy].symbolize_keys,
-
then: 0
else: 0
entity_argument_builder: context_params[:entity_argument_builder]&.safe_constantize,
-
show_results: context_params[:show_results],
-
show_selected: context_params[:show_selected],
-
)
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class ModelAssociationOverlay
-
# This class is responsible for creating new, attached, and missing items for the association overlay.
-
# It basically transforms your model into a Token, TagToken, or Entity, and wraps it in the appropriate component for
-
# the ModelAssociationOverlay (AttachedItem, MissingItem, NewItem)
-
# It also handles the creation of the form for creating new items
-
-
1
class ItemFactory < LoopComponent
-
1
param :context, type: Types.Instance(Context)
-
-
1
def create_new(**kwags)
-
then: 0
inner = if context.component_class == TagToken || context.component_class == Token
-
else: 0
context.component_class.new(text: "%new%", data: { "model-association-overlay-target": "newLabelWrapper" })
-
then: 0
elsif context.component_class < Entity
-
context.component_class.without_model(
-
text: "%new%",
-
data: { "model-association-overlay-target": "newLabelWrapper" },
-
)
-
else: 0
else
-
raise "Don't know how to create entry from #{context.component_class}!"
-
end
-
-
NewItem.new(context, entry: inner, **kwags)
-
end
-
-
1
def create_attached(item:, item_args: {})
-
then: 0
inner = if context.component_class == TagToken || context.component_class == Token
-
else: 0
then: 0
else: 0
context.component_class.new(text: context.case_sensitive? ? item.name : item.name.downcase, draggable: context.config[:draggable])
-
then: 0
elsif context.component_class < Entity
-
create_entity(item: item, item_args: { **item_args, draggable: context.config[:draggable] })
-
else: 0
else
-
raise "Don't know how to create entry from #{context.component_class} (with #{item})!"
-
end
-
-
then: 0
can_detach = if context.entity_argument_builder.present?
-
context.entity_argument_builder.new(item).can_detach?(policy: context.policy[:detach])
-
else: 0
else
-
context.policy[:detach]
-
end
-
-
else: 0
if can_detach
-
then: 0
# TODO: generalize this - probably the entity_argument_builder could be used to customize each item and it's actions
-
then: 0
if context.config.custom_detach_action.present?
-
inner.with_action do
-
tag.button(
-
class: "flex",
-
type: :button,
-
data: {
-
then: 0
else: 0
then: 0
else: 0
action: context.config.custom_detach_action&.dig("data_action") || context.config.custom_detach_action&.dig(:data_action),
-
then: 0
else: 0
then: 0
else: 0
controller: context.config.custom_detach_action&.dig("data_controller") || context.config.custom_detach_action&.dig(:data_controller),
-
item_id: item.id,
-
then: 0
else: 0
then: 0
else: 0
product_id: context.config.custom_detach_action&.dig("data_product_id") || context.config.custom_detach_action&.dig(:data_product_id) || nil,
-
then: 0
else: 0
then: 0
else: 0
**context.config.custom_detach_action&.dig("data_value") || context.config.custom_detach_action&.dig(:data_value) || {},
-
},
-
) do
-
tag.i(class: "fa-regular fa-xmark")
-
end
-
end
-
else: 0
else
-
inner.with_action do
-
tag.button(class: "flex", type: :submit) do
-
tag.i(class: "fa-regular fa-xmark")
-
end
-
end
-
end
-
end
-
-
AttachedItem.new(context, item: item, entry: inner)
-
end
-
-
1
def create_attached_for_list(item:)
-
then: 0
args = if context.entity_argument_builder.present?
-
context.entity_argument_builder.new(item).args
-
else: 0
else
-
{}
-
end
-
-
create_attached(item: item, item_args: args)
-
end
-
-
1
def create_missing(item:, **kwargs)
-
then: 0
inner = if context.component_class == TagToken || context.component_class == Token
-
else: 0
then: 0
else: 0
context.component_class.new(text: context.case_sensitive? ? item.name : item.name.downcase, locked: true)
-
then: 0
elsif context.component_class < Entity
-
create_entity(item: item)
-
else: 0
else
-
raise "Don't know how to create entry from #{context.component_class} (with from #{item})!"
-
end
-
-
MissingItem.new(context, item: item, entry: inner, **kwargs)
-
end
-
-
1
private
-
-
1
def create_entity(item:, item_args: {})
-
arg = context.component_class.instance_method(:initialize).parameters.first.second
-
context.component_class.new(arg => item, **item_args)
-
end
-
-
1
class AttachedItem < LoopComponent
-
1
param :context, type: Types.Instance(Context)
-
1
option :item, type: Types.Interface(:name, :id)
-
1
option :entry
-
-
1
def id
-
[
-
context.turbo_id,
-
"attached",
-
dom_id(item),
-
].join("_")
-
end
-
-
1
def attrs
-
{
-
class: selector,
-
data: {
-
model_association_overlay_target: "attachedItem",
-
then: 0
else: 0
label: context.case_sensitive? ? item.name : item.name.downcase,
-
},
-
}
-
end
-
-
1
def selector
-
id.to_s
-
end
-
end
-
-
1
class MissingItem < LoopComponent
-
1
param :context, type: Types.Instance(Context)
-
1
option :item, type: Types.Interface(:name, :id)
-
1
option :entry
-
-
1
option :hidden, default: -> { false }
-
-
1
def id
-
[
-
context.turbo_id,
-
"missing",
-
dom_id(item),
-
].join("_")
-
end
-
-
1
def attrs
-
{
-
class: [
-
"lui-association-overlay__results__cell",
-
"lui-association-overlay__results__cell--missing",
-
then: 0
else: 0
hidden ? "hidden!" : nil,
-
].compact.join(" "),
-
data: {
-
model_association_overlay_target: "result",
-
then: 0
else: 0
label: context.case_sensitive? ? item.name : item.name.downcase,
-
},
-
id: id,
-
}
-
end
-
end
-
-
1
class NewItem < LoopComponent
-
1
param :context, type: Types.Instance(Context)
-
1
option :entry
-
-
1
option :hidden, default: -> { true }
-
-
1
def id
-
[
-
context.turbo_id,
-
"new",
-
].join("_")
-
end
-
-
1
def attrs
-
{
-
then: 0
else: 0
class: "#{hidden ? "hidden! " : ""}lui-association-overlay__results__cell",
-
data: { model_association_overlay_target: "new" },
-
id: id,
-
}
-
end
-
-
1
def form_with_attrs
-
then: 0
if context.config.create_new.present?
-
context.config.create_new["form_attrs"].symbolize_keys.deep_merge!({
-
data: {
-
turbo_frame: "lui-main-layout",
-
turbo_action: "advance",
-
},
-
})
-
else: 0
else
-
{
-
scope: :new_association,
-
url: helpers.loopos_ui.associations_path,
-
method: :post,
-
}
-
end
-
end
-
-
1
def extra_params
-
extra = {}
-
then: 0
else: 0
extra.merge!(context.association_params) if context.association_params.present?
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
extra.merge!(context.config.create_new["payload"]) if context.config&.create_new&.dig("payload")&.present?
-
extra
-
end
-
end
-
end
-
end
-
end
-
<%= tag.div(**attrs) do %>
-
<%= form_with( url: helpers.loopos_ui.associations_path, method: :patch, class: "flex items-center h-full w-full") do |f| %>
-
<%= f.hidden_field :context, value: context.serialize %>
-
<%= f.hidden_field :association_id, value: item.id %>
-
<%= f.button(class: "flex items-center w-full h-full") do %>
-
<%= render entry %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= turbo_frame_tag turbo_id, class: "relative" do %>
-
<%= render LooposUi::AssociationOverlay.new(id: id, tag_options: association_overlay_tag_options, show_selected: show_selected, show_results: show_results) do |overlay| %>
-
<% overlay.with_selected_container do %>
-
<%= render LooposUi::AssociationOverlay::SelectedContainer.new(id: selected_container_id) do |c| %>
-
<% attached_items.each do |item| %>
-
<% c.with_selected_item do %>
-
<%= render item %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% overlay.with_results_container do %>
-
<%= render LooposUi::AssociationOverlay::ResultsContainer.new(id: results_container_id, show_results: show_results) do |c| %>
-
<% missing_items.each do |item| %>
-
<% c.with_result do %>
-
<%= render item %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= c.with_new_item do %>
-
<%= render new_item %>
-
<% end if can_create_new %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if header_icon.present? || header_title.present? || context.component_class < LooposUi::Entity %>
-
<% overlay.with_header(
-
icon: header_icon || context.component_class.icon,
-
title: header_title || context.component_class.name.demodulize)
-
%>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.div(**attrs) do %>
-
<div class="flex items-center w-full">
-
<div class="w-full">
-
<%= render entry %>
-
</div>
-
<%= form_with(**form_with_attrs, class: "flex items-center h-full w-full") do |f| %>
-
<%= hidden_field_tag :context, context.serialize %>
-
<% extra_params.each do |param,value| %>
-
<%= f.hidden_field param, value: value %>
-
<% end %>
-
<%= f.hidden_field :name, data: { "model-association-overlay-target": "newInput" } %>
-
<div class="flex w-full justify-end">
-
<%= render LooposUi::Button.new(
-
kind: :neutral,
-
type: :tertiary,
-
size: :tiny,
-
text: "Create New",
-
leading_icon: "fa-regular fa-plus",
-
tag_options: {
-
data: { action: "model-association-overlay#onResultsContainerChange" }
-
}
-
)
-
%>
-
</div>
-
<% end %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
class MultipleList < LoopComponent
-
1
erb_template <<~HTML
-
<div class="lui-multiple-list">
-
<%= content %>
-
</div>
-
HTML
-
end
-
end
-
1
module LooposUi
-
1
class PageHeader < LoopComponent
-
1
include Turbo::FramesHelper
-
1
include LooposUi::ResourceAware
-
-
1
renders_one :title_zone
-
-
1
renders_many :tokens # "LooposUi::Token"
-
1
renders_many :title_labels, types: {
-
manual: ->(&block) { capture(&block) },
-
counter: LooposUi::CounterLabel,
-
state: LooposUi::StateLabel,
-
double_state: LooposUi::DoubleStateLabel,
-
}
-
-
1
renders_many :details
-
-
1
attr_reader :title
-
-
# BACKCOMPATIBILITY - may be removed
-
1
renders_one :right_side
-
1
renders_one :bottom_side
-
1
attr_reader :top_page_args
-
-
# New APi
-
-
1
renders_one :header, LooposUi::Header
-
-
1
renders_one :image, LooposUi::V2::Image
-
1
renders_one :token_zone
-
-
# Do not use directly, use with_detail_zone
-
1
renders_many :_detail_zones
-
-
# TODO: replace with DRY::initializer
-
1
def initialize(title: nil, model: nil, **kwargs)
-
@title = title
-
@model = model
-
# BACKCOMPATIBILITY - may be removed
-
@kwargs = {
-
top_page_args: {
-
rounded: false,
-
image: false,
-
},
-
}.merge!(kwargs)
-
-
@top_page_args = @kwargs[:top_page_args]
-
@detail_zones_count = 0
-
end
-
-
1
def with_detail_zone(...)
-
@detail_zones_count += 1
-
-
then: 0
else: 0
raise ArgumentError, "You can't have more than 3 detail zones" if @detail_zones_count > 3
-
-
with__detail_zone(...)
-
end
-
-
1
def before_render
-
else: 0
then: 0
raise ArgumentError, "Header slot is required" unless header?
-
end
-
end
-
end
-
<turbo-frame id="lui-page-header" class="lui-page-header">
-
<div class="lui-page-header__container">
-
<div class="lui-page-header__container__tag_list">
-
<% tokens.each do |token| %>
-
<%= token %>
-
<% end %>
-
</div>
-
<div class="lui-page-header__container__header_zone">
-
<%= image %>
-
<div class="lui-page-header__container__header_zone___content">
-
<%= header %>
-
then: 0
else: 0
<% if details.present? %>
-
<div class="lui-page-header__container__header_zone___content__details">
-
<% details.each do |detail| %>
-
<%= detail %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
then: 0
else: 0
<% if right_side.present? %>
-
<div class="lui-page-header__right_side">
-
<%= right_side %>
-
</div>
-
<% end %>
-
</turbo-frame>
-
1
module LooposUi
-
1
module PageSection
-
1
class IdentifierEditor < LoopComponent
-
# There is a bug in dry-initializer: when undefined is false, the type checking constructor is not called
-
# eg:
-
# new() # will raise error, missing required argument
-
# new(source: "foobar") # will raise error, failed constraint check
-
# new(source: nil) # will NOT raise error
-
# Base LoopComponent uses undefined: false (performance optimizations, and now some components depend on it)
-
# but here we will opt out, source cannot be nil
-
1
extend Dry::Initializer[undefined: true]
-
-
1
Identifiable = ::Dry.Struct(id: Types::Coercible::Integer, type: Types::String)
-
1
IdentifierDisplay = Struct.new(:key_text, :text, keyword_init: true)
-
-
1
option :source, Types::Instance(LooposUi::PageSources::CoreSource)
-
-
-
# if not provided, it must be provided via JS
-
1
option :identifiable,
-
Types::Instance(Identifiable).constructor { |value|
-
case value
-
when: 0
when ::ActiveRecord::Base
-
Identifiable.new(id: value.id, type: value.class.name)
-
when: 0
when Hash
-
Identifiable.new(**value.symbolize_keys)
-
else: 0
else
-
raise "Invalid identifiable: #{value.inspect}"
-
end
-
},
-
optional: true
-
-
1
option :identifiable_type, Types::Coercible::String, optional: true
-
1
option :title, Types::Coercible::String, optional: true
-
-
1
def initialize(**kwargs)
-
super
-
then: 0
else: 0
if identifiable.blank? && @identifiable_type == Dry::Initializer::UNDEFINED
-
raise "identifiable or identifiable_type is required"
-
end
-
end
-
-
1
def kinds
-
source.identifiers.non_unique_kinds
-
end
-
-
1
def identifiable_type
-
then: 0
else: 0
identifiable&.type || @identifiable_type
-
end
-
-
1
def selected_container_id
-
then: 0
else: 0
"identifier-editor-selected-container-#{identifiable_type}-#{identifiable&.id}"
-
end
-
-
1
def result_for(kind)
-
Result.new(
-
kind: kind,
-
identifiable_type: identifiable_type,
-
identifiable: identifiable,
-
source: source,
-
)
-
end
-
-
1
def selected_item_for(kind, reference)
-
identifier_display = IdentifierDisplay.new(
-
key_text: I18n.t("admin.items.identifier_kinds.#{kind}"),
-
text: reference,
-
)
-
LooposUi::Entities::Identifier.new(identifier: identifier_display)
-
end
-
-
1
class << self
-
1
def identifiable_input_name
-
"identifier[identifiable_ids][]"
-
end
-
end
-
end
-
end
-
end
-
<%= tag.div(class: "contents", data: { controller: "lui--identifier-editor", "lui--identifier-editor-selected-container-id-value": selected_container_id }) do %>
-
<%= render LooposUi::AssociationOverlay.new(can_search: false) do |association_overlay| %>
-
<%= association_overlay.with_header(title: title.presence || t(".default_title")) %>
-
<%= association_overlay.with_selected_container do |selected_container| %>
-
<%= render LooposUi::AssociationOverlay::SelectedContainer.new(id: selected_container_id) do |sc| %>
-
<%# Steams will add the selected items %>
-
<% end %>
-
<% end %>
-
<%= association_overlay.with_results_container do |results_container| %>
-
<%= render LooposUi::AssociationOverlay::ResultsContainer.new do |rc| %>
-
<% kinds.each do |kind| %>
-
<% rc.with_result do |results| %>
-
<%= render result_for(kind) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.turbo_frame(id: turbo_frame_id, data: { "lui--identifier-editor-target": "result" }) do %>
-
<%= render LooposUi::Popover.new do |popover| %>
-
<% popover.with_custom_toggle do %>
-
<%= tag.div class: "lui-association-overlay__results__cell" do %>
-
<%= render LooposUi::EntityToken.new(text: I18n.t("admin.items.identifier_kinds.#{kind}")) %>
-
<% end %>
-
<% end %>
-
<%= tag.div(class: "lui-identifier-editor__form-entry") do %>
-
<%= render LooposUi::FormEntry.new(label: t("admin.items.identifier_editor.reference")) do |form_entry| %>
-
<%= form_entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "identifier[reference]",
-
mode: :form,
-
open_actions: true,
-
form: "identifier_#{kind}_form",
-
extra_input_attributes: {
-
data: {
-
action: "change->lui--identifier-editor#onInputChange keydown.enter->lui--identifier-editor#onKeyDown",
-
}
-
}
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= form_with id: "identifier_#{kind}_form",
-
url: helpers.loopos_ui.identifiers_create_path,
-
method: :post,
-
class: "hidden" do |form| %>
-
<%= form.hidden_field :context, value: source.source_context %>
-
<%= form.hidden_field "identifier[kind]", value: kind %>
-
<%= form.hidden_field "identifier[identifiable_type]", value: identifiable_type %>
-
then: 0
else: 0
<%= form.hidden_field("identifier[identifiable_id]", value: identifiable.id) if identifiable.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
class IdentifierEditor
-
1
class Result < LoopComponent
-
1
option :kind
-
1
option :identifiable_type
-
1
option :identifiable
-
1
option :source
-
-
1
def turbo_frame_id
-
"identifier_editor_result_#{identifiable_type}_#{kind}"
-
end
-
end
-
-
1
private_constant :Result
-
end
-
end
-
end
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class << self
-
1
def tab(key)
-
constants.filter_map do |subclass|
-
component = const_get(subclass)
-
then: 0
else: 0
component.const_get(:KEY) == key.to_sym ? component : nil
-
rescue NameError
-
nil
-
end.first
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "json"
-
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class AdvancedDetails < LoopComponent
-
1
KEY = :advanced_details
-
-
1
option :item
-
1
option :can_edit_extra_data, default: -> { false }
-
-
1
def extra_data_blocks
-
blocks = []
-
-
item.extra_datas.each do |extra_data|
-
blocks << {
-
title: block_title_for(extra_data),
-
key: extra_data.key,
-
data: extra_data.extra_data,
-
}
-
end
-
-
blocks
-
end
-
-
1
def sorted_extra_data_blocks
-
extra_data_blocks.sort_by { |block| block[:title].to_s.downcase }
-
end
-
-
1
def parsed_extra_data(data)
-
then: 0
else: 0
return data if data.is_a?(Hash) || data.is_a?(Array)
-
-
JSON.parse(data.to_s)
-
rescue JSON::ParserError, TypeError
-
data
-
end
-
-
1
def format_extra_data_value(value)
-
then: 0
else: 0
return "null" if value.nil?
-
-
value.to_s
-
end
-
-
1
private
-
-
1
def block_title_for(record)
-
raw = record.key.to_s.split("_").first
-
"#{raw.humanize} Extra Data"
-
end
-
end
-
end
-
end
-
end
-
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.v2.items.advanced_details.title"), size: "normal", description: I18n.t("admin.v2.items.advanced_details.label")) %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.v2.items.advanced_details.admin_details"), size: "small", underline: true) do |section| %>
-
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.v2.items.advanced_details.token"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<div class="flex flex-row" data-controller="clipboard-button" data-clipboard-button-copy-value="<%= item.token %>">
-
<%= render LooposUi::Inputs::Text.new(name: "token", value: item.token.presence || "-", readonly: true) %>
-
then: 0
else: 0
<%= render LooposUi::Button.new(leading_icon: "fa-regular fa-copy",
-
kind: :neutral, type: :tertiary,
-
tag_options: { data: { action:"click->clipboard-button#copy" } },
-
tooltip_text: I18n.t("admin.v2.copy"),
-
) if item.token.present? %>
-
</div>
-
<% end %>
-
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.v2.items.advanced_details.submission_url"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%# TODO: replace with lui component %>
-
<div class="flex flex-row justify-between" data-controller="clipboard-button" data-clipboard-button-copy-value="<%= item.submission_url %>">
-
<%= link_to item.submission_url, target: "_blank" do %>
-
<span class="lui-input__value link-underline-temp"> <%= item.submission_url %> </span>
-
<% end %>
-
<%= render LooposUi::Button.new(leading_icon: "fa-regular fa-copy",
-
kind: :neutral, type: :tertiary,
-
tag_options: { data: { action:"click->clipboard-button#copy" } },
-
tooltip_text: I18n.t("admin.v2.copy"),
-
) %>
-
</div>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<% if item.shopify_admin_product_url.present? %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.v2.items.advanced_details.shopify_url"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%# TODO: replace with lui component %>
-
<div class="flex flex-row justify-between" data-controller="clipboard-button" data-clipboard-button-copy-value="<%= item.shopify_admin_product_url %>">
-
<%= link_to item.shopify_admin_product_url, target: "_blank" do %>
-
<span class="lui-input__value link-underline-temp"> <%= item.shopify_admin_product_url %> </span>
-
<% end %>
-
<%= render LooposUi::Button.new(leading_icon: "fa-regular fa-copy",
-
kind: :neutral, type: :tertiary,
-
tag_options: { data: { action:"click->clipboard-button#copy" } },
-
tooltip_text: I18n.t("admin.v2.copy"),
-
) %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.v2.items.advanced_details.loopos_user_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<div class="flex flex-row">
-
<%= render LooposUi::Inputs::Text.new(name: "User ID", value: item.loopos_user_id || "-", readonly: true) %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.v2.items.advanced_details.extra_data"), size: "small", underline: true) do |section| %>
-
<% sorted_extra_data_blocks.each do |block| %>
-
<%= render LooposUi::Accordion.new(open: false, size: :small) do |accordion| %>
-
<% accordion.with_header(title: block[:title], size: :small) %>
-
<div class="pt-2">
-
<%= render LooposUi::ExtraDataViewer.new(
-
data: block[:data],
-
readonly: true
-
) %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Chat < LoopComponent
-
1
KEY = :chat
-
-
1
option :item
-
end
-
end
-
end
-
end
-
<%
-
public_unread = helpers.unread_message_count(@talkjs_public_user, item.token)
-
private_unread = helpers.unread_message_count(@talkjs_private_user, "#{item.token}-private")
-
%>
-
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(
-
title: I18n.t("admin.items.chat.messages_title"),
-
description: I18n.t("admin.items.chat.messages_description"),
-
size: "normal"
-
) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(
-
title: I18n.t("admin.items.tabs.chat"),
-
description: I18n.t("admin.items.chat.public_description"),
-
size: "small",
-
underline: true
-
) do %>
-
<%# Can only be here for now %>
-
then: 0
else: 0
<%= tag.div id: "public-tag", class: "mt-2", style: "visibility: #{public_unread > 0 ? '' : 'hidden'};" do %>
-
<%= render LooposUi::TagToken.new(text: helpers.unread_message(public_unread), color: :submission) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%# Can only be here for now %>
-
<%= render LooposUi::TabsSection.new(
-
title: I18n.t("admin.items.tabs.chat"),
-
description: I18n.t("admin.items.chat.internal_description"),
-
size: "small",
-
underline: true
-
) do %>
-
then: 0
else: 0
<%= tag.div id: "private-tag", class: "mt-2", style: "visibility: #{private_unread > 0 ? '' : 'hidden'};" do %>
-
<%= render LooposUi::TagToken.new(text: helpers.unread_message(private_unread), color: :submission) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<div class="public-chat flex flex-col items-start gap-6 flex-1">
-
<div class="flex flex-col gap-[4px] self-start w-full">
-
<%= react_component("ItemChat", {
-
itemToken: item.token,
-
userId: helpers.current_user.id,
-
appId: ::ItemChatConfig.app_id,
-
apiKey: ::ItemChatConfig.api_key,
-
type: "public",
-
locale: I18n.locale.to_s,
-
then: 0
else: 0
then: 0
else: 0
userName: ::LoopOsManager::Client::User.get_personal_data(loopos_user_id: helpers.current_user.id)&.body&.dig("personal_data", "full_name") || "LoopOS || Core",
-
}, {
-
style: "width: 100%; min-width: 453px"
-
}
-
) %>
-
<%#= render LooposUi::Chat.new(
-
item_token: @item.token,
-
user_id: current_user.id,
-
app_id: ItemChatConfig.app_id,
-
api_key: ItemChatConfig.api_key,
-
type: "public",
-
user_name: LoopOsManager::Client::User.get_personal_data(loopos_user_id: current_user.id)&.body&.dig("personal_data", "full_name") || "LoopOS || Core",
-
locale: I18n.locale.to_s,
-
style:"width: 100%; min-width: 453px"
-
) %>
-
</div>
-
</div>
-
<% end %>
-
<% row.with_column do %>
-
<div class="private-chat flex flex-col items-start gap-6 flex-1">
-
<div class="flex flex-col gap-[4px] self-start w-full">
-
<%= react_component("ItemChat", {
-
itemToken: item.token,
-
userId: helpers.current_user.id,
-
appId: ::ItemChatConfig.app_id,
-
apiKey: ::ItemChatConfig.api_key,
-
type: "private",
-
locale: I18n.locale.to_s,
-
then: 0
else: 0
then: 0
else: 0
userName: ::LoopOsManager::Client::User.get_personal_data(loopos_user_id: helpers.current_user.id)&.body&.dig("personal_data", "full_name") || "LoopOS || Core",
-
}, {
-
style: "width: 100%; min-width: 453px"
-
}
-
) %>
-
<%#= render LooposUi::Chat.new(
-
item_token: @item.token,
-
user_id: current_user.id,
-
app_id: ItemChatConfig.app_id,
-
api_key: ItemChatConfig.api_key,
-
type: "private",
-
user_name: LoopOsManager::Client::User.get_personal_data(loopos_user_id: current_user.id)&.body&.dig("personal_data", "full_name") || "LoopOS || Core",
-
locale: I18n.locale.to_s,
-
style:"width: 100%; min-width: 453px"
-
) %>
-
</div>
-
</div>
-
<script>
-
function hideTags() {
-
const publicTag = document.getElementById('public-tag');
-
const privateTag = document.getElementById('private-tag');
-
-
if (publicTag) publicTag.style.visibility = 'hidden';
-
if (privateTag) privateTag.style.visibility = 'hidden';
-
}
-
-
function checkTags() {
-
if (!document.hidden) {
-
hideTags();
-
}
-
}
-
-
document.addEventListener('visibilitychange', checkTags);
-
document.addEventListener('DOMContentLoaded', checkTags);
-
setTimeout(checkTags, 3000);
-
</script>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class CustomerInfo < LoopComponent
-
1
KEY = :client_info
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
1
option :uncensored_seller, Types::Bool, default: -> { false }
-
1
option :uncensored_buyer, Types::Bool, default: -> { false }
-
1
option :policy, Types::Hash, default: -> { { edit_info: false, edit_iban: false } }
-
-
1
def user_protocol_answers
-
@user_protocol_answers ||= @item.user_protocol_answers(uncensored: uncensored_seller)
-
end
-
-
1
def buyer_info
-
@buyer_info ||= begin
-
# Buyer info comes as a slug => value hash.
-
# Keep protocol-order keys first, then append remaining keys.
-
-
original_info = @item.buyer_info(uncensored: uncensored_buyer)
-
else: 0
then: 0
unless original_info.is_a?(Hash) && original_info.dig("error").present?
-
ordered = user_protocol_answers.each_with_object([]) do |answer, result|
-
slug = answer[:protocol_element][:slug]
-
else: 0
then: 0
next unless original_info.key?(slug)
-
-
result << { slug:, value: original_info[slug] }
-
end
-
-
ignored_keys = [
-
*ordered.pluck(:slug),
-
"user_id",
-
]
-
-
remaining = original_info.except(*ignored_keys).map { |slug, value| { slug:, value: } }
-
ordered + remaining
-
end
-
end
-
end
-
-
1
def protocol_answer_form_dom_id(answer)
-
"client_info_protocol_answer_#{answer[:protocol_element][:id]}_form"
-
end
-
-
1
def buyer_label_for(answer)
-
{
-
full_name: I18n.t("admin.items.client_info.full_name"),
-
email: I18n.t("admin.items.client_info.email"),
-
phone_number: I18n.t("admin.items.client_info.phone_number"),
-
address_street: I18n.t("admin.items.client_info.address_street"),
-
address_additional_info: I18n.t("admin.items.client_info.address_additional_info"),
-
address_postal_code: I18n.t("admin.items.client_info.address_postal_code"),
-
address_city: I18n.t("admin.items.client_info.address_city"),
-
address_country: I18n.t("admin.items.client_info.address_country"),
-
nif: I18n.t("admin.items.client_info.nif"),
-
iban: I18n.t("admin.items.client_info.iban"),
-
user_id: I18n.t("admin.items.client_info.user_id"),
-
}[answer[:slug].to_sym]
-
end
-
-
1
private
-
-
INPUT_TYPE_MAPPING = {
-
1
text: Inputs::Text,
-
number: Inputs::Number,
-
date: Inputs::Date,
-
select: Inputs::Select2,
-
bool: Inputs::Checkbox,
-
images: V2::Image,
-
files: Inputs::File,
-
}.freeze
-
-
1
def input_type_for(answer)
-
type = answer[:protocol_element][:type].split(":").last.to_sym
-
INPUT_TYPE_MAPPING.fetch(type, Inputs::Text)
-
end
-
-
1
def input_for(answer, index = 0)
-
input_type = input_type_for(answer)
-
then: 0
else: 0
main_value = main_answer_for(answer)&.dig(:value)
-
then: 0
else: 0
display_value = main_value.presence || (readonly?(answer) ? "-" : "")
-
-
case input_type
-
when: 0
when ->(t) { t == Inputs::Checkbox && !uncensored_seller }
-
Inputs::Text.new(name: input_name(index), value: "*", readonly: true)
-
when: 0
when ->(t) { t == V2::Image }
-
attrs = {
-
name: protocol_answer_values_field_name(index),
-
then: 0
else: 0
image_url: display_value == "-" || display_value.blank? ? nil : display_value.first[:url],
-
editable: !readonly?(answer),
-
form_id: protocol_answer_form_dom_id(answer),
-
}
-
then: 0
else: 0
if @item.source.is_a?(LooposUi::PageSources::CoreApi)
-
client = @item.source.core_client
-
base = client.base_url.to_s.chomp("/")
-
attrs[:direct_upload_url] = "#{base}/rails/active_storage/direct_uploads"
-
attrs[:direct_upload_authorization] = "Bearer #{client.token}"
-
end
-
V2::Image.new(**attrs)
-
when: 0
when ->(t) { t == Inputs::File }
-
Inputs::File.new(
-
name: protocol_answer_values_field_name(index),
-
then: 0
else: 0
value: display_value == "-" || display_value.blank? ? nil : display_value.first[:url],
-
accept: ["pdf", "jpg", "jpeg", "png", "webp"],
-
mode: :autosubmit,
-
readonly: readonly?(answer),
-
)
-
when: 0
when ->(t) { t == Inputs::Date }
-
Inputs::Date.new(
-
name: input_name(index),
-
value: display_value,
-
mode: :autosubmit,
-
kind: "DateTimePicker",
-
readonly: readonly?(answer),
-
)
-
else: 0
else
-
input_type.new(
-
name: input_name(index),
-
value: display_value,
-
then: 0
else: 0
readonly: input_type == Inputs::Checkbox ? true : readonly?(answer),
-
)
-
end
-
end
-
-
1
def input_name(index)
-
"protocol_answers[#{index}][value]"
-
end
-
-
# Image/file protocol answers submit Active Storage signed ids as `values: ["<signed_id>"]`
-
# (form field name `protocol_answers[n][values][]`).
-
1
def protocol_answer_values_field_name(index)
-
"protocol_answers[#{index}][values][]"
-
end
-
-
1
def main_answer_for(answer)
-
answer[:answers].find { |a| a[:main_answer] }
-
end
-
-
1
def answer_slug(answer)
-
answer[:protocol_element][:slug]
-
end
-
-
1
def readonly?(answer)
-
else: 0
then: 0
return true unless uncensored_seller
-
-
then: 0
else: 0
can_edit = answer_slug(answer) == "iban" ? policy[:edit_iban] : policy[:edit_info]
-
-
else: 0
then: 0
return true unless can_edit
-
-
["terms_and_conditions", "allows_data_share"].include?(answer_slug(answer))
-
end
-
-
1
def client_info_answer_groups
-
group_size = user_protocol_answers.count / 2 + user_protocol_answers.count % 2
-
[
-
user_protocol_answers[0...group_size],
-
user_protocol_answers[group_size..],
-
].compact_blank
-
end
-
-
1
def buyer_info_groups
-
group_size = buyer_info.count / 2 + buyer_info.count % 2
-
[
-
buyer_info[0...group_size],
-
buyer_info[group_size..],
-
].compact_blank
-
end
-
end
-
end
-
end
-
end
-
<%
-
seller_attrs = {
-
then: 0
else: 0
text: uncensored_seller ? I18n.t("admin.items.client_info.unsee_data") : I18n.t("admin.items.client_info.see_data"),
-
size: :tiny,
-
type: :secondary,
-
kind: :neutral,
-
href: LooposUi::Engine.routes.url_helpers.client_info_item_path(
-
uncensored_seller: !uncensored_seller,
-
uncensored_buyer: uncensored_buyer,
-
token: item.token,
-
context: item.source.source_context)
-
}
-
-
buyer_attrs = {
-
then: 0
else: 0
text: uncensored_buyer ? I18n.t("admin.items.client_info.unsee_data") : I18n.t("admin.items.client_info.see_data"),
-
size: :tiny,
-
type: :secondary,
-
kind: :neutral,
-
href: LooposUi::Engine.routes.url_helpers.client_info_item_path(
-
uncensored_seller: uncensored_seller,
-
uncensored_buyer: !uncensored_buyer,
-
token: item.token,
-
context: item.source.source_context)
-
}
-
-
%>
-
<%# FIXME: turbo frame id should be dynamic %>
-
<turbo-frame id="item_customer_info">
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
then: 0
else: 0
<% if user_protocol_answers.present? %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(
-
title: I18n.t("admin.items.client_info.seller_info.title"), description: I18n.t("admin.items.client_info.seller_info.label"),
-
size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::ActionButtons.new do |c| %>
-
<% c.with_button_group do |g| %>
-
<% g.with_button(**seller_attrs) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FlexLayout.new(size: :half) do |layout| %>
-
<% client_info_answer_groups.each do |group| %>
-
<% layout.with_section do %>
-
<% group.each_with_index do |answer, index| %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.client_info.#{answer[:protocol_element][:slug]}", default: answer[:protocol_element][:label]), required: false, orientation: "horizontal", label_width: 168) do |entry| %>
-
<%= entry.with_input do %>
-
<%= tag.turbo_frame id: "#{answer[:protocol_element][:id]}_protocol_answer" do %>
-
<%= form_with url: LooposUi::Engine.routes.url_helpers.update_protocol_answer_item_path(
-
token: item.token,
-
context: item.source.source_context,
-
uncensored: uncensored_seller,
-
), method: :patch, multipart: true, html: { id: protocol_answer_form_dom_id(answer) }, data: { turbo_frame: "#{answer[:protocol_element][:id]}_protocol_answer" } do |form| %>
-
<%# Text/select/bool use protocol_answers[n][value]; images/files use protocol_answers[n][values][] (signed blob ids). %>
-
<%= form.hidden_field "protocol_answers[#{index}][protocol_element_id]", value: answer[:protocol_element][:id] %>
-
<%= render input_for(answer, index) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if buyer_info.present? %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.client_info.buyer_info.title"), description: I18n.t("admin.items.client_info.buyer_info.label"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::ActionButtons.new do |c| %>
-
<% c.with_button_group do |g| %>
-
<% g.with_button(**buyer_attrs) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FlexLayout.new(size: :half) do |layout| %>
-
<% buyer_info_groups.each do |group| %>
-
<% layout.with_section do %>
-
<% group.each do |answer| %>
-
<%= render LooposUi::FormEntry.new(label: buyer_label_for(answer), required: false, orientation: "horizontal", label_width: 168) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: answer[:slug],
-
value: answer[:value].presence || "-",
-
readonly: true
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.client_info.no_data"), size: "normal" ) %>
-
<% end %>
-
<%# DO NOT SHOW BUYER INFO AND SELLER INFO IF THERE IS NO DATA %>
-
<% end if !buyer_info.present? && !user_protocol_answers.present? && false %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_client_info_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</turbo-frame>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class FinancialData < LoopComponent
-
1
KEY = :financial_data
-
-
1
option :item
-
1
option :policy, optional: true
-
-
1
class OtherValuesSection < LoopComponent
-
1
option :item
-
1
option :policy, optional: true
-
-
MANUAL_VALUE_DIRECTIONS = [
-
1
"legal",
-
"logistics",
-
"other",
-
"remainder",
-
"repair",
-
"reserve",
-
"transport",
-
].freeze
-
-
1
def manual_value_kind_options
-
MANUAL_VALUE_DIRECTIONS.sort.map do |dir|
-
{
-
value: dir,
-
text: I18n.t("admin.items.financial_data.value_kinds.other_values.#{dir}", default: dir.titleize),
-
}
-
end
-
end
-
-
1
def manual_item_values
-
MANUAL_VALUE_DIRECTIONS.flat_map do |dir|
-
Array(@item.public_send("#{dir}_item_values"))
-
rescue NoMethodError
-
[]
-
end
-
end
-
end
-
-
1
class ItemValueCard < LoopComponent
-
1
option :item
-
1
option :type, default: proc { "in" }
-
1
option :item_value
-
1
option :source
-
1
option :policy, optional: true
-
-
1
def apps_icons
-
[
-
"core",
-
"submission",
-
"hubs",
-
"validation",
-
"handling",
-
"manager",
-
"validation-rails",
-
"handling-rails",
-
]
-
end
-
-
end
-
-
1
class ItemValueTable < LoopComponent
-
1
option :forward_trade_in, default: proc { false }
-
1
option :item_value
-
1
option :type, optional: true
-
1
option :item, optional: true
-
end
-
-
1
class TransactionCard < LoopComponent
-
1
option :item_id
-
1
option :total
-
1
option :eligible_payments
-
1
option :amount
-
1
option :payments_type, type: Types::Coercible::Symbol.enum(:incoming, :outgoing)
-
1
option :card_id
-
end
-
-
1
class ItemMarketplaceValueOutCard < LoopComponent
-
1
option :item_id
-
1
option :amount
-
1
option :block_name
-
1
option :value_out
-
1
option :card_id
-
1
option :token, optional: true
-
1
option :context, optional: true
-
end
-
-
1
class TransactionsList < LoopComponent
-
1
option :transactions
-
1
option :item
-
end
-
-
1
class LogsList < LoopComponent
-
1
option :logs
-
1
option :item
-
end
-
-
1
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
delegate :in_item_values, :out_item_values, :extra_item_values, :grouped_logs, :repair_item_values, :forward_item, :payments, :incoming_payments, to: :@item
-
-
1
def price_estimation_logs
-
price_estimation_logs = grouped_logs.group_by { |log| log[:main_target].split(" ").first == "PriceEstimation" }
-
logs_per_date = price_estimation_logs.group_by { |log| log[:created_at].strftime("%d-%m-%Y") }
-
logs_per_date.map do |date, logs|
-
{
-
date: date,
-
logs: logs.map { |log| transform_log(log) },
-
}
-
end
-
[]
-
end
-
-
1
def item_value_apps_select
-
select_options = LoopOsManager::Applications.app_instances_info.map do |_id, info|
-
{
-
value: info[:label],
-
text: info[:label],
-
}
-
end
-
-
select_options.unshift({
-
value: "all",
-
text: I18n.t("admin.items.financial_data.all_apps"),
-
})
-
-
select_options << {
-
value: "System",
-
text: I18n.t("admin.items.financial_data.system_app"),
-
}
-
-
select_options
-
end
-
-
1
def item_value_kinds_select
-
base = [
-
{
-
value: "all",
-
text: I18n.t("admin.items.financial_data.all_kinds"),
-
},
-
{
-
value: "ItemValueIn",
-
text: I18n.t("admin.items.financial_data.value_kinds.item_value_in"),
-
},
-
{
-
value: "ItemValueOut",
-
text: I18n.t("admin.items.financial_data.value_kinds.item_value_out"),
-
},
-
{
-
value: "Services::Payment",
-
text: I18n.t("admin.items.financial_data.value_kinds.payment"),
-
},
-
{
-
value: "Services::IncomingPayment",
-
text: I18n.t("admin.items.financial_data.value_kinds.incoming_payment"),
-
},
-
{
-
value: "PriceEstimation",
-
text: I18n.t("admin.items.financial_data.value_kinds.price_estimation"),
-
},
-
{
-
value: "ItemValueAuction",
-
text: I18n.t("admin.items.financial_data.value_kinds.item_value_auction"),
-
},
-
]
-
-
manual = OtherValuesSection::MANUAL_VALUE_DIRECTIONS.sort.map do |dir|
-
{
-
value: dir,
-
text: I18n.t("admin.items.financial_data.value_kinds.other_values.#{dir}", default: dir.to_s.titleize),
-
}
-
end
-
-
base + manual
-
end
-
-
1
def transform_log(log)
-
title = nil
-
extra_message = nil
-
-
then: 0
if log["main_target_type"] == "ItemValue" && log["main_target_id"].present?
-
item_value =
-
begin
-
version = PaperTrail::Version
-
.where(item_type: "ItemValue", item_id: log["main_target_id"])
-
.where("created_at <= ?", log["created_at"])
-
.order(created_at: :desc)
-
.first
-
then: 0
else: 0
version&.reify || ItemValue.find_by(id: log["main_target_id"])
-
rescue NameError
-
# PaperTrail not loaded in this context; fall back to current record.
-
ItemValue.find_by(id: log["main_target_id"])
-
end
-
then: 0
else: 0
direction = item_value&.direction.to_s
-
-
then: 0
else: 0
if item_value
-
extra_message = ApplicationController.renderer.render(
-
partial: "admin/v2/items/item_value_table",
-
locals: {
-
item_value: item_value,
-
as_of: log["created_at"],
-
table_log_id: log["id"],
-
},
-
)
-
end
-
-
title =
-
case direction
-
when: 0
when "in"
-
I18n.t("admin.items.financial_data.value_kinds.item_value_in")
-
when: 0
when "out"
-
I18n.t("admin.items.financial_data.value_kinds.item_value_out")
-
when: 0
when "extra"
-
I18n.t("admin.items.financial_data.value_kinds.extra_value")
-
else: 0
else
-
then: 0
else: 0
if OtherValuesSection::MANUAL_VALUE_DIRECTIONS.include?(direction)
-
I18n.t("admin.items.financial_data.value_kinds.other_values.#{direction}", default: direction.to_s.titleize)
-
end
-
else: 0
end
-
elsif log["main_target_type"] == "PriceEstimation"
-
then: 0
# rubocop:disable Style/OpenStructUse
-
estimation = OpenStruct.new(
-
amount: Money.new(log.dig("extra_data", "estimation_status", "estimated_value", "cents") || 0, Money.default_currency.iso_code),
-
amount_details: log.dig("extra_data", "estimation_status", "estimated_details") || [],
-
created_at: log["created_at"],
-
)
-
# rubocop:enable Style/OpenStructUse
-
extra_message = ApplicationController.renderer.render(
-
partial: "admin/v2/items/item_value_table",
-
locals: {
-
item_value: estimation,
-
as_of: nil,
-
table_log_id: log["id"],
-
},
-
)
-
else: 0
title = I18n.t("admin.items.financial_data.value_kinds.price_estimation")
-
then: 0
elsif log["main_target_type"] == "Services::Payment"
-
else: 0
title = I18n.t("admin.items.financial_data.value_kinds.payment")
-
then: 0
else: 0
elsif log["main_target_type"] == "Services::IncomingPayment"
-
title = I18n.t("admin.items.financial_data.value_kinds.incoming_payment")
-
end
-
-
{
-
time: log["created_at"].strftime("%H:%M"),
-
author: log["user"],
-
title: title || log["main_target_type"],
-
app_instance: {
-
name: LoopOsManager::Applications.app_instances_info.dig(log["app_instance_id"], :label) ||
-
I18n.t("admin.items.financial_data.system_app"),
-
kind: LoopOsManager::Applications.app_instances_info.dig(log["app_instance_id"], :kind) || "neutral",
-
},
-
message: log["message"],
-
error: log["level"] == "error",
-
extra_data: log["extra_data"],
-
item_value_id: log["main_target_id"],
-
extra_message: extra_message,
-
}
-
end
-
-
1
then: 0
else: 0
delegate :source, to: :@item
-
end
-
end
-
end
-
end
-
<%
-
# Handle in_item_values hash
-
curr_in_item_values = in_item_values
-
first_in_item_value = curr_in_item_values.first
-
other_in_item_values = curr_in_item_values[1..]
-
-
# Handle out_item_values hash
-
curr_out_item_values = out_item_values
-
first_out_item_value = curr_out_item_values.first
-
other_out_item_values = curr_out_item_values[1..]
-
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
price_estimation_log = grouped_logs.find { |log| log[:logs]&.last&.dig(:main_target)&.split(" ")&.first == "PriceEstimation" }
-
-
curr_extra_item_values = extra_item_values
-
first_extra_item_value = curr_extra_item_values.first
-
other_extra_item_values = curr_extra_item_values[1..]
-
-
curr_repair_item_values = repair_item_values
-
first_repair_item_value = curr_repair_item_values.first
-
other_repair_item_values = curr_repair_item_values[1..]
-
-
has_marketplace_value_out = item.marketplace_value_out.present?
-
%>
-
-
<%= render LooposUi::TabsContent.new do |tabs| %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.financial_data.title"), size: "normal", description: I18n.t("admin.items.financial_data.label")) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<% if price_estimation_log.present? || curr_in_item_values&.any? || curr_out_item_values&.any? || forward_item.present? || has_marketplace_value_out %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.proposals.title"), size: "small", underline: true) do %>
-
<div class="flex flex-row gap-4">
-
then: 0
<% if curr_in_item_values.any? %>
-
else: 0
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueCard.new(item: item, item_value: first_in_item_value, type: "in", source: source, policy: policy) %>
-
then: 0
else: 0
<% elsif price_estimation_log.present? %>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueCard.new(item: item, item_value: price_estimation_log, type: "price_estimation", source: source, policy: policy) %>
-
<% end %>
-
then: 0
else: 0
<% if curr_out_item_values.any? %>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueCard.new(item: item, item_value: first_out_item_value, type: "out", source: source, policy: policy) %>
-
<% end %>
-
then: 0
else: 0
<% if curr_extra_item_values.any? %>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueCard.new(item: item, item_value: first_extra_item_value, type: "top-up", source: source, policy: policy) %>
-
<% end %>
-
then: 0
else: 0
<% if has_marketplace_value_out %>
-
<%= render LooposUi::V2::Card.new(
-
id: "marketplace-value-out",
-
title: I18n.t("admin.items.financial_data.marketplace_value_out"),
-
description: I18n.t("admin.items.financial_data.marketplace_value_out_description"),
-
src: LooposUi::Engine.routes.url_helpers.marketplace_value_out_item_path(token: item.token, context: source.source_context),
-
) do |card| %>
-
<% card.with_corner do %>
-
<%= render LooposUi::Loadings::Skeleton::Bar.new(width: "100px") %>
-
<% end %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= tag.turbo_frame id: "other-values-#{item.token}", src: LooposUi::Engine.routes.url_helpers.other_values_item_path(token: item.token, context: source.source_context) do %>
-
<div class="flex flex-row gap-4">
-
<%= render LooposUi::V2::Card.new(
-
id: "other-values-#{item.token}",
-
) do |card| %>
-
<% card.with_title_description do |td| %>
-
<% end %>
-
<% card.with_corner do %>
-
<%= render LooposUi::Loadings::Skeleton::Bar.new(width: "100px") %>
-
<% end %>
-
<% card.with_custom_description do %>
-
<%= render LooposUi::Loadings::Skeleton::Bar.new %>
-
<% end %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.financial_data.transactions"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "transactions-#{item.token}", src: LooposUi::Engine.routes.url_helpers.transactions_item_path(token: item.token, context: source.source_context) do %>
-
<div class="flex flex-row gap-4">
-
<%= render LooposUi::V2::Card.new(
-
id: "transactions-#{item.token}",
-
) do |card| %>
-
<% card.with_title_description do |td| %>
-
<% end %>
-
<% card.with_corner do %>
-
<%= render LooposUi::Loadings::Skeleton::Bar.new(width: "100px") %>
-
<% end %>
-
<% card.with_custom_description do %>
-
<%= render LooposUi::Loadings::Skeleton::Bar.new %>
-
<% end %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= form_with url: LooposUi::Engine.routes.url_helpers.financial_logs_item_path(token: item.token), method: :get, id: "log-list-form", data: { turbo_frame: "log-list-frame" } do |form| %>
-
<%= form.hidden_field :context, value: source.source_context %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.financial_data.financial_history"), size: "small", underline: true) do |section| %>
-
then: 0
else: 0
<% section.with_corner_action do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.financial_data.apps"), required: false, tooltip: nil, orientation: "horizontal") do |f| %>
-
<% f.with_input do %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: "app",
-
options: item_value_apps_select,
-
value: "all",
-
placeholder: I18n.t("admin.items.financial_data.select_app"),
-
open_actions: true,
-
mode: :autosubmit,
-
form: "log-list-form"
-
) %>
-
<% end %>
-
<% end %>
-
<% end if LooposUi.config.app_type?(:core) %>
-
<%# FIXME: UI doesnt have LoopOsManager::Applications, which means it breaks when its used by an app that natively doesnt have them %>
-
<% section.with_corner_action do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.financial_data.value_kind"), required: false, tooltip: nil, orientation: "horizontal") do |f| %>
-
<% f.with_input do %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: "kind",
-
options: item_value_kinds_select,
-
value: "all",
-
placeholder: I18n.t("admin.items.financial_data.select_value_kind"),
-
open_actions: true,
-
mode: :autosubmit,
-
form: "log-list-form"
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.turbo_frame id: "log-list-frame", src: LooposUi::Engine.routes.url_helpers.financial_logs_item_path(token: item.token, context: source.source_context) do %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_financial_data_tab, instance_id: item.id, title: I18n.t("admin.items.proposals.custom_information", default: "Custom information")) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% header_info = OpenStruct.new(
-
title: I18n.t("admin.items.financial_data.marketplace_value_out"),
-
description: I18n.t("admin.items.financial_data.marketplace_value_out_description")
-
)
-
card_item_value = OpenStruct.new(
-
amount: value_out.amount,
-
status: value_out.status,
-
updated_at: value_out.created_at,
-
)
-
%>
-
<%= render LooposUi::V2::Card::FinancialLog.new(id: @card_id, header_info: header_info, item_value: card_item_value) do |card| %>
-
<% card.with_title_info do %>
-
<%= render LooposUi::Label.new(text: block_name, icon: "fa-regular fa-store") %>
-
<% end %>
-
<% card.with_modal(modal_width: 800) do |modal| %>
-
<div class="flex flex-col w-full overflow-y-auto">
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueTable.new(item_value: card_item_value) %>
-
<%= render LooposUi::Accordion.new(title: I18n.t("admin.items.item_value_card.history")) do |accordion| %>
-
<%= tag.turbo_frame id: "modal-log-history-#{token}-itemvalueauction", src: LooposUi::Engine.routes.url_helpers.financial_logs_modal_item_path(token: token, context: context, kind: "ItemValueAuction") do %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
-
<%
-
#TODO: Move everything to the view component
-
header_info = OpenStruct.new(
-
then: 0
else: 0
title: type == "in" ? I18n.t("admin.items.item_value_card.value_in") : I18n.t("admin.items.item_value_card.value_out"),
-
description: I18n.t("admin.items.item_value_card.last_proposed_value"))
-
block_data = {}
-
card_item_value = item_value
-
then: 0
else: 0
item_value_id = item_value.is_a?(Hash) ? (item_value["id"] || item_value[:id]) : item_value.try(:id)
-
manual_other_value = %w[repair logistics legal transport remainder reserve other].include?(type.to_s)
-
then: 0
else: 0
if item.forward_item.present?
-
then: 0
else: 0
then: 0
else: 0
proposal_details = item.forward_item.is_a?(Hash) ? item.forward_item["proposal_details"] : item.forward_item&.proposal_details
-
then: 0
else: 0
then: 0
else: 0
currency = item.forward_item.is_a?(Hash) ? item.forward_item["currency"] || "EUR" : item.forward_item&.info[:currency] || "EUR"
-
end
-
-
then: 0
if type == "top-up"
-
header_info.title = I18n.t("admin.items.item_value_card.top_up")
-
header_info.description = I18n.t("admin.items.item_value_card.extra_benefits_amount")
-
-
then: 0
if card_item_value.blank?
-
discount_detail = proposal_details.find { |detail| detail[:label] == "Discount value" || detail[:label] == "Extra value" }
-
then: 0
else: 0
amount_value = discount_detail&.dig(:value)
-
card_item_value = OpenStruct.new(
-
amount: Money.new(amount_value.presence.to_f.abs, currency),
-
then: 0
else: 0
status: item_value.is_a?(Hash) ? item_value.dig("status") : item_value.status,
-
then: 0
else: 0
updated_at: item_value.is_a?(Hash) ? item_value[:created_at] : item_value.try(:updated_at),
-
else: 0
)
-
else: 0
elsif card_item_value.is_a?(Hash)
-
then: 0
# Normaliza hashes existentes para OpenStruct + Money
-
card_item_value = OpenStruct.new(card_item_value)
-
card_item_value.amount = Monetize.parse(card_item_value.amount, currency)
-
card_item_value.updated_at = card_item_value[:created_at]
-
end
-
else: 0
-
then: 0
elsif type == "price_estimation"
-
header_info.title = I18n.t("admin.items.item_value_card.price_estimation")
-
header_info.description = I18n.t("admin.items.item_value_card.price_estimation_description")
-
block_data = {
-
name: item_value.extra_data.dig("estimation_status", "block_name"),
-
kind: LooposUi::BlockTypeIcons::BLOCK_TYPE_TO_ICON[item_value.extra_data.dig("estimation_status", "block_type")] || item_value.extra_data.dig("estimation_status", "block_type"),
-
}
-
card_item_value = OpenStruct.new(
-
amount: Money.new(item_value.extra_data.dig("estimation_status", "estimated_value", "cents") || 0, "EUR"),
-
amount_details: item_value.extra_data.dig("estimation_status", "estimated_details") || [],
-
created_at: item_value.created_at,
-
# status: I18n.t("models.item_value.status.#{item_value.status}")
-
else: 0
)
-
then: 0
elsif type == "repair"
-
header_info.title = I18n.t("admin.items.financial_data.value_kinds.other_values.repair", default: "Repair")
-
user_description =
-
then: 0
if item_value.is_a?(Hash)
-
then: 0
else: 0
item_value["description"].presence || Array(item_value["amount_details"]).first&.dig("label") || ""
-
else: 0
else
-
then: 0
else: 0
item_value.try(:description).presence || item_value.try(:amount_details).to_a.first&.dig("label") || ""
-
end
-
header_info.description = user_description.presence || ""
-
-
then: 0
else: 0
if card_item_value.is_a?(Hash)
-
card_item_value = OpenStruct.new(card_item_value)
-
card_item_value.amount = Monetize.parse(card_item_value.amount, card_item_value.currency)
-
card_item_value.updated_at = card_item_value[:created_at]
-
else: 0
end
-
then: 0
elsif manual_other_value
-
header_info.title = I18n.t("admin.items.financial_data.value_kinds.other_values.#{type}", default: type.to_s.titleize)
-
user_description =
-
then: 0
if item_value.is_a?(Hash)
-
then: 0
else: 0
item_value["description"].presence || Array(item_value["amount_details"]).first&.dig("label") || ""
-
else: 0
else
-
then: 0
else: 0
item_value.try(:description).presence || item_value.try(:amount_details).to_a.first&.dig("label") || ""
-
end
-
header_info.description = user_description.presence || ""
-
-
then: 0
else: 0
if card_item_value.is_a?(Hash)
-
card_item_value = OpenStruct.new(card_item_value)
-
card_item_value.amount = Monetize.parse(card_item_value.amount, card_item_value.currency)
-
card_item_value.updated_at = card_item_value[:created_at]
-
end
-
else: 0
else
-
then: 0
if item_value.is_a?(Hash)
-
card_item_value = OpenStruct.new(item_value)
-
card_item_value.amount = Monetize.parse(card_item_value.amount, card_item_value.currency)
-
card_item_value.updated_at = item_value[:created_at]
-
-
creation_log = item_value[:logs].sort_by { |log| log[:created_at] }.first
-
else: 0
else
-
card_item_value = item_value
-
creation_log = item_value.logs.sort_by { |log| log.created_at }.first
-
end
-
then: 0
else: 0
if creation_log.present?
-
block_data = {
-
name: creation_log[:extra_data].dig("item_value_status", "block_name"),
-
kind: LooposUi::BlockTypeIcons::BLOCK_TYPE_TO_ICON[creation_log[:extra_data].dig("item_value_status", "block_type")] || creation_log[:extra_data].dig("item_value_status", "block_type"),
-
}
-
end
-
end
-
-
# For manual "other values", we also want to show the app label (same as other cards).
-
then: 0
else: 0
if manual_other_value && item_value_id.present?
-
creation_log =
-
then: 0
if item_value.is_a?(Hash)
-
Array(item_value[:logs] || item_value["logs"]).sort_by { |log| log[:created_at].to_s }.first
-
else: 0
else
-
item_value.logs.sort_by { |log| log.created_at }.first
-
end
-
-
then: 0
else: 0
if creation_log.present?
-
then: 0
else: 0
bd = creation_log.is_a?(Hash) ? creation_log.with_indifferent_access : creation_log
-
extra =
-
then: 0
if bd.respond_to?(:extra_data)
-
bd.extra_data
-
else: 0
else
-
bd[:extra_data]
-
end
-
extra = (extra || {}).with_indifferent_access
-
block_data = {
-
name: extra.dig(:item_value_status, :block_name),
-
kind: LooposUi::BlockTypeIcons::BLOCK_TYPE_TO_ICON[extra.dig(:item_value_status, :block_type)] || extra.dig(:item_value_status, :block_type),
-
}
-
end
-
end
-
show_fti = (type == "in" || type == "price_estimation") && item.forward_item.present?
-
then: 0
else: 0
raw_status = item_value.is_a?(Hash) ? item_value["status"] || item_value[:status] : item_value.try(:status)
-
deleted_manual = manual_other_value && raw_status.to_s == "cancelled"
-
then: 0
else: 0
item_value_id = item_value.is_a?(Hash) ? (item_value["id"] || item_value[:id]) : item_value.try(:id)
-
-
then: 0
else: 0
can_update_other = policy.respond_to?(:can_update_other_item_values?) ? policy.can_update_other_item_values? : true
-
then: 0
else: 0
can_cancel_other = policy.respond_to?(:can_cancel_other_item_values?) ? policy.can_cancel_other_item_values? : true
-
-
# "Other values" are always saved as agreed, but we do not show the state label.
-
then: 0
if manual_other_value && card_item_value.respond_to?(:status=)
-
else: 0
card_item_value.status = nil
-
then: 0
else: 0
elsif manual_other_value && card_item_value.is_a?(Hash)
-
card_item_value = card_item_value.except("status", :status)
-
end
-
%>
-
<%= render LooposUi::V2::Card::FinancialLog.new(header_info: header_info, item_value: card_item_value, fti: show_fti, draft: type == "price_estimation") do |card| %>
-
then: 0
else: 0
<% if manual_other_value && can_cancel_other && type.to_s != "repair" && !deleted_manual && item_value_id.present? %>
-
<% card.with_corner_action do %>
-
<%= render LooposUi::Modal.new(
-
title: I18n.t("admin.items.financial_data.manual_item_value.delete_modal_title"),
-
description: I18n.t("admin.items.financial_data.manual_item_value.delete_modal_description"),
-
) do |modal| %>
-
<% modal.with_trigger_button(
-
icon: "trash",
-
tooltip_text: I18n.t("admin.items.financial_data.manual_item_value.delete_tooltip"),
-
size: :small,
-
kind: :neutral,
-
type: :secondary,
-
) %>
-
<% modal.with_primary_action(
-
text: I18n.t("admin.items.financial_data.manual_item_value.delete_confirm"),
-
kind: :danger,
-
href: LooposUi::Engine.routes.url_helpers.cancel_manual_item_value_item_path(
-
token: item.token,
-
id: item_value_id,
-
context: source.source_context,
-
),
-
tag_options: {
-
"data-turbo-method": :patch,
-
turbo_frame: "other-values-#{item.token}",
-
},
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if block_data.present? %>
-
<% card.with_title_info do %>
-
then: 0
<% if apps_icons.include?(block_data[:kind]) %>
-
<%= render LooposUi::Entities::AppInstance.from_hash(**block_data) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Label.new(text: block_data[:name], icon: block_data[:kind]) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% card.with_modal(modal_width: 800, show_footer: manual_other_value && can_update_other && item_value_id.present?, form_id: (manual_other_value && can_update_other && item_value_id.present?) ? "edit-manual-item-value-#{item.token}-#{item_value_id}" : nil) do |modal| %>
-
<div class="flex flex-col w-full overflow-y-auto">
-
<% if manual_other_value && item_value_id.present? %>
-
then: 0
<%
-
edit_form_id = "edit-manual-item-value-#{item.token}-#{item_value_id}"
-
current_description =
-
then: 0
if item_value.is_a?(Hash)
-
then: 0
else: 0
item_value["description"].presence || Array(item_value["amount_details"]).first&.dig("label") || ""
-
else: 0
else
-
then: 0
else: 0
item_value.try(:description).presence || item_value.try(:amount_details).to_a.first&.dig("label") || ""
-
end
-
current_amount =
-
then: 0
if item_value.is_a?(Hash)
-
item_value["float_amount"] || item_value["amount"] || ""
-
else: 0
else
-
item_value.try(:float_amount) || item_value.try(:amount).try(:to_f) || ""
-
end
-
%>
-
<%= form_with(
-
url: LooposUi::Engine.routes.url_helpers.update_manual_item_value_item_path(
-
token: item.token,
-
id: item_value_id,
-
context: source.source_context,
-
),
-
method: :patch,
-
id: edit_form_id,
-
data: {
-
turbo_frame: "other-values-#{item.token}",
-
controller: "manual-item-value-edit-modal",
-
},
-
) do |f| %>
-
<%= hidden_field_tag :context, source.source_context %>
-
<%= hidden_field_tag "item_value[kind]", type.to_s %>
-
-
<div class="w-full">
-
<%= render LooposUi::V2::Table.new(
-
id: "manual-item-value-edit-table-#{item.token}-#{item_value_id}",
-
columns: [
-
{ title: I18n.t("admin.items.financial_data.manual_item_value.table_description"), dataIndex: :description },
-
{ title: I18n.t("admin.items.financial_data.manual_item_value.table_value"), dataIndex: :amount },
-
],
-
show_pagination: false,
-
show_result_count: false,
-
searchable: false,
-
) do |table| %>
-
<% table.with_row(key: "edit-manual-item-value") do |row| %>
-
<% row.with_cell(property: :description) do %>
-
then: 0
<% if can_update_other %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "item_value[description]",
-
value: current_description,
-
maxlength: 100,
-
placeholder: I18n.t("admin.items.financial_data.manual_item_value.description_placeholder"),
-
mode: :form,
-
form: edit_form_id,
-
) %>
-
else: 0
<% else %>
-
<span class="text-gray-900"><%= current_description.presence || "-" %></span>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :amount) do %>
-
then: 0
<% if can_update_other %>
-
<%= render LooposUi::Inputs::Number.new(
-
name: "item_value[amount]",
-
value: current_amount,
-
step: "0.01",
-
with_actions: false,
-
mode: :form,
-
form: edit_form_id,
-
) %>
-
else: 0
<% else %>
-
<span class="text-gray-900"><%= current_amount.presence || "-" %></span>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
-
then: 0
else: 0
<% if can_update_other %>
-
<% modal.with_cancel_button(text: I18n.t("modal.cancel")) %>
-
<% modal.with_primary_action(text: I18n.t("admin.items.financial_data.manual_item_value.save"), tag_options: { type: :submit, form: edit_form_id }) %>
-
else: 0
<% end %>
-
then: 0
<% elsif card_item_value.amount_details.present? %>
-
else: 0
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueTable.new(item_value: card_item_value) %>
-
then: 0
<% elsif type == "top-up" %>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueTable.new(item_value: card_item_value, type: type, item: item) %>
-
else: 0
<% else %>
-
<p class="text-gray-500"><%= I18n.t("admin.items.item_value_card.no_price_details_available") %></p>
-
<% end %>
-
then: 0
else: 0
<% if show_fti %>
-
then: 0
else: 0
<%= render LooposUi::Accordion.new(title: I18n.t("admin.items.item_value_card.forward_trade_in", product_name: item.forward_item.is_a?(Hash) ? item.forward_item["forward_product_name"] : item.forward_item.product.name)) do |accordion| %>
-
<% accordion.with_body do %>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueTable.new(item_value: card_item_value, item: item, forward_trade_in: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<%
-
history_modal_query = { context: source.source_context }
-
then: 0
if item_value_id.present?
-
history_modal_query[:main_target_type] = "ItemValue"
-
else: 0
history_modal_query[:main_target_id] = item_value_id
-
then: 0
elsif type == "price_estimation"
-
then: 0
else: 0
last_log = (item_value[:logs] || item_value["logs"])&.last
-
then: 0
else: 0
then: 0
else: 0
pe_id = last_log&.dig(:main_target_id) || last_log&.dig("main_target_id")
-
then: 0
else: 0
if pe_id.blank? && last_log
-
raw_mt = (last_log[:main_target] || last_log["main_target"]).to_s
-
m = raw_mt.match(/#(\d+)/)
-
then: 0
else: 0
pe_id = m[1] if m
-
end
-
then: 0
if pe_id.present?
-
history_modal_query[:main_target_type] = "PriceEstimation"
-
history_modal_query[:main_target_id] = pe_id
-
else: 0
else
-
history_modal_query[:kind] = "PriceEstimation"
-
else: 0
end
-
then: 0
elsif type == "in"
-
else: 0
history_modal_query[:kind] = "ItemValueIn"
-
then: 0
elsif type == "out"
-
history_modal_query[:kind] = "ItemValueOut"
-
else: 0
else
-
history_modal_query[:kind] = "ItemValueOut"
-
end
-
-
history_frame_id = [
-
"modal-log-history",
-
item.token.to_s,
-
(item_value_id || history_modal_query[:main_target_id] || type).to_s,
-
].join("-").gsub(/[^\w-]/, "-")
-
%>
-
<%= render LooposUi::Accordion.new(title: I18n.t("admin.items.item_value_card.history")) do |accordion| %>
-
<%= tag.turbo_frame id: history_frame_id, src: LooposUi::Engine.routes.url_helpers.financial_logs_modal_item_path({ token: item.token }.merge(history_modal_query)) do %>
-
<%= render LooposUi::Loadings::Skeleton.new(n_rows: 2) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% if forward_trade_in %>
-
then: 0
<%
-
then: 0
else: 0
proposal_details = item.forward_item.is_a?(Hash) ? item.forward_item["proposal_details"] : item.forward_item.proposal_details
-
type = type || "forward-trade-in"
-
-
table_data = if type == "top-up"
-
then: 0
# For top-up: show only discount value
-
then: 0
if item_value.present?
-
table_rows = item_value.amount_details.map do |detail|
-
detail = detail.with_indifferent_access
-
OpenStruct.new(
-
characteristic: detail[:label],
-
value: detail[:value] || detail[:value_in]
-
)
-
end
-
table_total = item_value.amount_cents
-
else: 0
else
-
discount_detail = proposal_details.find { |detail| detail[:label] == "Discount value" || detail[:label] == "Extra value" }
-
then: 0
else: 0
table_rows = discount_detail ? [OpenStruct.new(characteristic: discount_detail[:label], value: discount_detail[:value])] : []
-
then: 0
else: 0
then: 0
else: 0
table_total = discount_detail&.dig(:value)&.abs || 0
-
end
-
{
-
rows: table_rows,
-
total: table_total,
-
}
-
else
-
else: 0
# For regular forward trade-in: show all details except final estimation
-
regular_rows = proposal_details.reject { |detail| detail[:label] == "Final estimation" || detail[:label] == "Total" }
-
final_estimation = proposal_details.find { |detail| detail[:label] == "Final estimation" || detail[:label] == "Total" }
-
-
then: 0
else: 0
total = final_estimation&.dig(:value) || 0
-
{
-
then: 0
else: 0
then: 0
else: 0
rows: regular_rows.map { |detail| OpenStruct.new(characteristic: detail[:label], value: detail[:value]&.to_money&.to_f) },
-
then: 0
else: 0
total: total&.to_money.to_f
-
}
-
end
-
-
-
table_args = {
-
data: table_data[:rows],
-
show_pagination: false,
-
show_result_count: false,
-
searchable: false,
-
columns: [
-
then: 0
else: 0
{ title: type == "top-up" ? I18n.t("admin.items.forward_trade_in_table.top_up_details") : I18n.t("admin.items.forward_trade_in_table.fti_details"), dataIndex: :characteristic },
-
{ title: I18n.t("admin.items.forward_trade_in_table.value"), dataIndex: :value }
-
],
-
id: "forward_trade_in_table",
-
}
-
%>
-
-
<div class="flex flex-col">
-
<%= render LooposUi::V2::Table.new(**table_args) do |table| %>
-
<% table_data[:rows].each do |row| %>
-
<% table.with_row(key: row.characteristic) do |table_row| %>
-
<% table_row.with_cell(property: :characteristic) do %>
-
<%= row.characteristic %>
-
<% end %>
-
<% table_row.with_cell(property: :value) do %>
-
then: 0
else: 0
<%= Money.new(row.value.abs, item.forward_item.is_a?(Hash) ? item.forward_item["currency"] : item.forward_item.info[:currency]).format %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% table.with_row(key: "total") do |total_row| %>
-
<% total_row.with_cell(property: :characteristic) do %>
-
<span class="text-sm font-semibold text-general-global-black">
-
<%= I18n.t("admin.items.forward_trade_in_table.total") %>
-
</span>
-
<% end %>
-
<% total_row.with_cell(property: :value) do %>
-
<span class="text-lg font-semibold text-general-global-black">
-
then: 0
else: 0
then: 0
else: 0
<%= table_data[:total] ? Money.new(table_data[:total], item.forward_item.is_a?(Hash) ? item.forward_item["currency"] : item.forward_item.info[:currency]).format : '--' %>
-
</span>
-
<% end %>
-
<% end %>
-
<% end %>
-
<div class="flex flex-row items-center justify-end mb-3">
-
<span class="lui-financial_card-content-info__timestamp">
-
then: 0
else: 0
<%= I18n.t("admin.items.item_value_table.created_at") %> <%= render LooposUi::DateShow.new(date: item.forward_item.is_a?(Hash) ? item.forward_item["created_at"] : item.forward_item.created_at) %>
-
</span>
-
</div>
-
</div>
-
<% else %>
-
else: 0
<%
-
then: 0
else: 0
table_data = item_value.amount_details&.map do |detail|
-
detail = detail.with_indifferent_access
-
OpenStruct.new(
-
characteristic: detail[:label],
-
value: detail[:value] || detail[:value_in]
-
)
-
end
-
-
table_args = {
-
data: table_data,
-
show_pagination: false,
-
show_result_count: false,
-
searchable: false,
-
columns: [
-
{ title: I18n.t("admin.items.item_value_table.details"), dataIndex: :details, width: 200 },
-
{ title: I18n.t("admin.items.item_value_table.value"), dataIndex: :value, width: 200 },
-
],
-
id: "price_details_table_#{item_value.id}"
-
}
-
%>
-
<div class="flex flex-col">
-
<%= render LooposUi::V2::Table.new(**table_args) do |table| %>
-
then: 0
else: 0
<% table_data&.each do |detail| %>
-
<% table.with_row(key: detail.characteristic) do |row| %>
-
<% row.with_cell(property: :details) do %>
-
<%= detail.characteristic %>
-
<% end %>
-
<% row.with_cell(property: :value) do %>
-
<% v = detail.value %>
-
then: 0
<% if v.is_a?(Numeric) %>
-
else: 0
<%= Money.new(v, item_value.amount_currency).format %>
-
then: 0
<% elsif v.present? %>
-
<%= v %>
-
else: 0
<% else %>
-
<%= item_value.amount.format %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% table.with_row(key: "total") do |row| %>
-
<% row.with_cell(property: :details) do %>
-
<%= tag.span(I18n.t("admin.items.item_value_table.total"), class: "text-sm font-semibold text-general-global-black") %>
-
<% end %>
-
<% row.with_cell(property: :value) do %>
-
<%= tag.span(item_value.amount.format, class: "text-lg font-semibold text-general-global-black") %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<div class="flex flex-row items-center justify-end mb-3">
-
<%= tag.span(class: "lui-financial_card-content-info__timestamp") do %>
-
<%= I18n.t("admin.items.item_value_table.created_at") %>
-
<%= render LooposUi::DateShow.new(date: item_value.created_at) %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<%= tag.turbo_frame id: "log-list-frame" do %>
-
then: 0
<% if logs.present? %>
-
<%= render LooposUi::LogList.from_any_source(
-
logs,
-
id: "preview-log-list",
-
page: 1,
-
) %>
-
else: 0
<% else %>
-
<%= tag.div(I18n.t("admin.items.financial_data.no_logs_found"), class: "copy-14 text-general-global-black my-3") %>
-
<% end %>
-
<% end %>
-
<%= tag.turbo_frame id: "other-values-#{item.token}" do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.financial_data.manual_item_value.other_values_section_title"), size: "small", underline: true) do |ts| %>
-
<% ts.with_corner_action do %>
-
then: 0
else: 0
<% can_create = policy.respond_to?(:can_create_other_item_values?) ? policy.can_create_other_item_values? : true %>
-
<% form_id = "manual-item-value-form-#{item.token}" %>
-
<% modal_id = "manual-item-value-modal-#{item.token}" %>
-
<% modal_trigger_id = "#{modal_id}-open-trigger" %>
-
<div class="flex items-center gap-2">
-
then: 0
else: 0
<% if can_create %>
-
<% menu_options = manual_value_kind_options.map do |opt|
-
{
-
text: opt[:text],
-
attributes: {
-
data: {
-
action: "click->manual-item-value-modal#openFromMenu",
-
manual_item_value_modal_kind_param: opt[:value],
-
manual_item_value_modal_modal_id_param: modal_id,
-
manual_item_value_modal_modal_trigger_id_param: modal_trigger_id,
-
},
-
},
-
}
-
end %>
-
-
<%= render LooposUi::ActionMenu.new(options: menu_options, portal_data_controllers: ["manual-item-value-modal"]) do |menu| %>
-
<% menu.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
text: I18n.t("admin.items.financial_data.manual_item_value.add_value_button"),
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
leading_icon: "fa-regular fa-plus",
-
trailing_icon: "fa-regular fa-chevron-down",
-
tag_options: { type: :button },
-
) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::Modal.new(id: modal_id, modal_width: 720, form_id: form_id) do |modal| %>
-
<% modal.with_trigger do %>
-
<button id="<%= modal_trigger_id %>" type="button" class="hidden" data-action="click->modal#open">
-
<%= I18n.t("admin.items.financial_data.manual_item_value.open_modal") %>
-
</button>
-
<% end %>
-
-
<% modal.with_header(title: I18n.t("admin.items.financial_data.manual_item_value.add_modal_title", kind: I18n.t("admin.items.financial_data.value_kinds.other_values.repair", default: "Repair"))) %>
-
-
<%= form_with(
-
url: LooposUi::Engine.routes.url_helpers.manual_item_values_item_path(token: item.token),
-
method: :post,
-
id: form_id,
-
data: { turbo_frame: "other-values-#{item.token}" },
-
) do |f| %>
-
then: 0
else: 0
<%= hidden_field_tag :context, item.source.source_context if item.source.respond_to?(:source_context) %>
-
-
<%= tag.div(
-
class: "flex flex-col gap-4",
-
data: {
-
controller: "manual-item-value-modal",
-
manual_item_value_modal_title_template_value: I18n.t("admin.items.financial_data.manual_item_value.add_modal_title", kind: "%{kind}"),
-
manual_item_value_modal_kind_labels_value: manual_value_kind_options.to_h { |opt| [opt[:value].to_s, opt[:text].to_s] },
-
manual_item_value_modal_require_amount_value: true,
-
},
-
) do %>
-
<%= hidden_field_tag "item_value[kind]", "repair" %>
-
<div class="w-full">
-
<%= render LooposUi::V2::Table.new(
-
id: "manual-item-value-table-#{item.token}",
-
columns: [
-
{ title: I18n.t("admin.items.financial_data.manual_item_value.table_description"), dataIndex: :description },
-
{ title: I18n.t("admin.items.financial_data.manual_item_value.table_value"), dataIndex: :amount },
-
],
-
show_pagination: false,
-
show_result_count: false,
-
searchable: false,
-
) do |table| %>
-
<% table.with_row(key: "new-manual-item-value") do |row| %>
-
<% row.with_cell(property: :description) do %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "item_value[description]",
-
value: "",
-
maxlength: 100,
-
placeholder: I18n.t("admin.items.financial_data.manual_item_value.description_placeholder"),
-
mode: :form,
-
form: form_id,
-
) %>
-
<% end %>
-
<% row.with_cell(property: :amount) do %>
-
<%= render LooposUi::Inputs::Number.new(
-
name: "item_value[amount]",
-
value: "",
-
step: "0.01",
-
with_actions: false,
-
mode: :form,
-
form: form_id,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
-
<% modal.with_cancel_button(text: I18n.t("modal.cancel")) %>
-
<% modal.with_primary_action(text: I18n.t("admin.items.financial_data.manual_item_value.save"), tag_options: { type: :submit, form: form_id, disabled: true }) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
-
<% values = manual_item_values.reject { |iv|
-
then: 0
else: 0
status = (iv.is_a?(Hash) ? (iv["status"] || iv[:status]) : iv.try(:status)).to_s
-
status == "cancelled"
-
} %>
-
then: 0
<% if values.any? %>
-
<div class="flex flex-row flex-wrap gap-4">
-
then: 0
else: 0
<% values.sort_by { |v| (v.is_a?(Hash) ? v["created_at"].to_s : v.created_at.to_s) }.each do |iv| %>
-
<%
-
then: 0
else: 0
type = (iv.is_a?(Hash) ? iv["direction"] : iv.direction).to_s
-
%>
-
<%= render LooposUi::PageSection::ItemTab::FinancialData::ItemValueCard.new(item: item, item_value: iv, type: type, source: item.source, policy: policy) %>
-
<% end %>
-
</div>
-
else: 0
<% else %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.financial_data.manual_item_value.empty"), size: "small") %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<%= tag.turbo_frame id: "transactions-#{item.token}" do %>
-
then: 0
<% if @transactions.any? %>
-
<div class="flex flex-row gap-4">
-
<% @transactions.each do |transaction| %>
-
<%
-
type = transaction.class.name.demodulize.underscore
-
financial_transaction = LooposUi::V2::Card::FinancialTransaction::FinancialTransaction.build(
-
then: 0
else: 0
incoming_payment: type == "incoming_payment" ? transaction : nil,
-
then: 0
else: 0
outgoing_payment: type == "payment" ? transaction : nil
-
)
-
%>
-
<%= render LooposUi::V2::Card::FinancialTransaction.new(
-
id: "transaction-#{1}",
-
financial_transaction: financial_transaction
-
)
-
%>
-
<% end %>
-
</div>
-
else: 0
<% else %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("no_data"), size: "small" ) %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Flow < LoopComponent
-
1
KEY = :flow
-
-
1
option :item
-
1
option :policy
-
-
1
def flow_presenter
-
::Flows::FlowPresenter.new(item.flow, view_from: item)
-
end
-
-
1
def product_presenter
-
item.product.presenter
-
end
-
-
1
def oauth_core_token
-
LoopOsManager::Applications.get_oauth_core_token
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.v2.items.flow.title"), description: I18n.t("admin.v2.items.flow.label"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
then: 0
<% if policy.can_view_flows? %>
-
<div class="info-tab__section info-tab__section--flow">
-
then: 0
<% if flow_presenter.present? %>
-
<div data-controller="flow-block-redirect" data-focus="<%= item.current_block_id %>">
-
<%= react_component(
-
"FlowProvider",
-
{
-
direction: "LR",
-
flowData: flow_presenter.serialize,
-
flowId: flow_presenter.id,
-
token: oauth_core_token,
-
highlightedStates: flow_presenter.highlighted_states,
-
},
-
class: "flow__wrapper",
-
data: { 'height-observer-target': "flow" }
-
)
-
%>
-
</div>
-
<turbo-frame id="settings_flow_sidebar" data-controller="flow-sidebar">
-
</turbo-frame>
-
else: 0
<% else %>
-
<p class="tabs__empty">Item without flow</p>
-
<% end %>
-
</div>
-
else: 0
<% else %>
-
<%= t('.unauthorized') %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class ImpactWidget < LoopComponent
-
1
KEY = :impact_widget
-
-
1
option :item
-
-
1
def tab_info
-
item.calculate_impact_for_item_page
-
end
-
-
1
def impact_label(value, key)
-
"#{format_number(value)}#{::Catalog::Node::IMPACT_SCHEMA[key][:unit]}"
-
end
-
-
1
def format_number(number)
-
_i = number.to_i
-
f = number.to_f
-
number_with_precision(f, precision: 2, separator: ",")
-
end
-
end
-
end
-
end
-
end
-
<%
-
item_impact = tab_info[:item_impact]
-
category_impact = tab_info[:category_impact]
-
%>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: "LoopOS Impact", description: I18n.t("admin.v2.impact.page.description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.v2.items.impact"), size: "small", underline: true) do %>
-
<% Catalog::Node::IMPACT_SCHEMA.keys.each do |impact_type| %>
-
<%= render LooposUi::DataCard.new(
-
title: I18n.t("admin.v2.impact.page.#{impact_type}.title"),
-
icon: Catalog::Node::IMPACT_SCHEMA[impact_type][:icon],
-
balance_value: format_number(item_impact[impact_type][:balance_value]),
-
reuse_cost: impact_label(item_impact[impact_type][:reuse_cost], impact_type),
-
saved_value: impact_label(item_impact[impact_type][:saved_value], impact_type),
-
metric_unit: I18n.t("admin.v2.impact.page.#{impact_type}.measure")
-
)%>
-
<% end %>
-
<%= render partial: 'admin/v2/impacts/card_labels' %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render partial: "/admin/v2/impacts/impact_cards", locals: {impact: category_impact}%>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Info < LoopComponent
-
1
KEY = :item_info
-
-
1
option :item
-
1
option :policy, default: -> { {} }, type: Types::Hash
-
-
1
class IdentifiersTable < LoopComponent
-
1
option :identifiers
-
1
option :readonly, default: -> { true }, type: Types::Bool
-
1
option :source_context, optional: true
-
1
option :identifiable_id, optional: true
-
1
option :identifiable_type, optional: true
-
1
option :available_kinds, optional: true, default: -> { [] }
-
end
-
-
1
def show_identifiers?
-
!LooposUi.config.app_type?(:core)
-
end
-
-
1
private
-
-
1
def fetch_available_kinds
-
else: 0
then: 0
return [] unless policy[:manage_identifiers]
-
-
kinds_data = item.source.identifiers.kinds
-
then: 0
if kinds_data.present?
-
kinds_data.map { |k| { value: k, text: I18n.t("admin.items.identifier_kinds.#{k}") } }
-
else: 0
else
-
[]
-
end
-
rescue => e
-
LooposUi.logger.error("Failed to fetch available identifier kinds: #{e.message}")
-
[]
-
end
-
end
-
end
-
end
-
end
-
<%
-
columns = [
-
{ title: t(".identifiers_table.kind"), sortable: false, dataIndex: "kind", key: "kind"},
-
{ title: t(".identifiers_table.reference"), sortable: false, dataIndex: "reference", key: "reference"},
-
]
-
-
table_options = {
-
show_result_count: false,
-
searchable: false,
-
}
-
-
else: 0
then: 0
unless readonly
-
table_options[:manageable_rows] = true
-
table_options[:add_new_url] = LooposUi::Engine.routes.url_helpers.identifiers_table_new_path(
-
context: source_context,
-
identifiable_id: identifiable_id,
-
identifiable_type: identifiable_type,
-
)
-
end
-
%>
-
<%= render LooposUi::V2::Table.new(id: "identifiers_table", columns: columns, **table_options) do |table| %>
-
then: 0
<% if readonly %>
-
<% identifiers.each_with_index do |identifier, index| %>
-
<% identifier.each do |kind, reference| %>
-
<% table.with_row(key: index) do |row| %>
-
<% row.with_cell(property: :kind) do %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "identifier[kind]",
-
value: t("admin.items.identifier_kinds.#{kind}"),
-
readonly: true
-
) %>
-
<% end %>
-
<% row.with_cell(property: :reference) do %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "identifier[reference]",
-
value: reference,
-
readonly: true
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<% identifiers.each do |identifier| %>
-
<%
-
update_url = LooposUi::Engine.routes.url_helpers.identifiers_update_path(
-
id: identifier.id,
-
context: source_context,
-
)
-
destroy_url = LooposUi::Engine.routes.url_helpers.identifiers_destroy_path(
-
id: identifier.id,
-
context: source_context,
-
)
-
%>
-
<% table.with_row(key: identifier.id, delete_action: { url: destroy_url }) do |row| %>
-
<% row.with_cell(property: :kind) do %>
-
<%= form_with(url: update_url, method: :patch, id: "form_kind_#{identifier.id}") do |form| %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: "identifier[kind]",
-
options: available_kinds,
-
value: identifier.kind,
-
mode: :autosubmit,
-
form: "form_kind_#{identifier.id}",
-
readonly: false
-
) %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :reference) do %>
-
<%= form_with(url: update_url, method: :patch, id: "form_reference_#{identifier.id}") do |form| %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "identifier[reference]",
-
value: identifier.reference,
-
mode: :autosubmit,
-
form: "form_reference_#{identifier.id}",
-
readonly: false
-
) %>
-
<% end %>
-
<% end %>
-
<% row.with_action %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.item_info.title"), description: I18n.t("admin.items.item_info.description"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.item_info.item_details.title"), size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.external_reference"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "external_reference", value: item.external_reference, readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.category"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
then: 0
else: 0
<% item_categories = item.respond_to?(:categories_names) ? item.categories_names : item.categories %>
-
<% item_categories.each do |category_name| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: OpenStruct.new(name: category_name)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.brand"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.brand.present? %>
-
then: 0
else: 0
<%= render LooposUi::Entities::Brand.new(brand: OpenStruct.new(name: item.respond_to?(:brand_name) ? item.brand_name : item.brand)) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "brand", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.product"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.product.present? %>
-
then: 0
else: 0
<%= render LooposUi::Entities::Product.new(product: OpenStruct.new(name: item.respond_to?(:product_name) ? item.product_name : item.product)) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.option_values"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.option_values.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
<% item.option_values.each do |option| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::EntityToken.new(text: "#{option[:value] || option[:name]}") %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "option_values_empty", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.variant"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.variant_name.present? %>
-
<%= render LooposUi::Token.new(text: item.variant_name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "variant", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.hubs_store"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Entities::AppInstance.new(app_instance: OpenStruct.new(name: item.hub_origin_name, kind: "hubs")) %>
-
<% end %>
-
<% end if item.hub_origin_name.present? %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.submission_origin"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Entities::AppInstance.new(app_instance: OpenStruct.new(name: item.submission_origin_data[:label], kind: "submission")) %>
-
<% end %>
-
<% end if item.submission_origin_data.present? %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.created_at"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: item.created_at) %>
-
<% end if item.created_at.present? %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.updated_at"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: item.updated_at) %>
-
<% end if item.updated_at.present? %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.days_created"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "days_created", value: item.days_created, readonly: true) %>
-
<% end if item.days_created.present? %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.days_in_current_state"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "days_in_current_state", value: item.days_in_current_state, readonly: true) %>
-
<% end if item.days_in_current_state.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.item_info.timeline"), size: "small", underline: true) do %>
-
else: 0
then: 0
<% unless item.new_record? %>
-
then: 0
else: 0
<% item.timeline&.each_with_index do |timepoint, index| %>
-
<%= render LooposUi::Timeline.new(
-
item: timepoint,
-
is_last: (item.timeline.size - 1) == index
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.item_info.internal_notes.title"), size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.internal_notes.rejection_reason"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "rejection_reason", value: item.rejection_reason_text.presence || item.rejection_reason.presence || "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::GridLayout.new(cols: 1) do |tab| %>
-
then: 0
else: 0
<% item.internal_notes&.each do |app, note| %>
-
<% tab.with_section do %>
-
<%= render LooposUi::Accordion.new(open: true) do |accordion| %>
-
<% accordion.with_header(title: app, size: :tiny) %>
-
<%= render LooposUi::Inputs::RichText.new(name: "notes_#{app.downcase}", value: note.presence || "-" , readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if item.internal_notes.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
then: 0
else: 0
<% row.with_column(half: true) do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.item_info.identifiers.title"), size: "small", underline: "yes") do %>
-
then: 0
<% if policy[:manage_identifiers] %>
-
<%= render LooposUi::PageSection::ItemTab::Info::IdentifiersTable.new(
-
identifiers: item.identifier_records,
-
readonly: false,
-
source_context: item.source.source_context,
-
identifiable_id: item.id,
-
identifiable_type: "Item",
-
available_kinds: fetch_available_kinds,
-
) %>
-
else: 0
<% else %>
-
<%= render LooposUi::PageSection::ItemTab::Info::IdentifiersTable.new(identifiers: item.identifiers) %>
-
<% end %>
-
<% end %>
-
<% end if show_identifiers? %>
-
<% row.with_column(half: show_identifiers?) do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_item_info_tab, instance_id: item.id, title: I18n.t("admin.items.item_info.custom_information")) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Logs < LoopComponent
-
1
PER_PAGE = 10
-
1
KEY = :logs
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
-
# used for pagination
-
1
option :page, Types::Integer.default(1), optional: true
-
1
option :per_page, Types::Integer.default(PER_PAGE), optional: true
-
1
option :has_more, Types::Bool.default(false), optional: true
-
1
option :total_count, Types::Integer, optional: true
-
1
option :grouped_logs, Types::Array, optional: true
-
-
# used for filtering
-
1
option :filter_block_id, optional: true
-
1
option :filter_user_type, optional: true
-
1
option :filter_date_range, Types::Coercible::String, optional: true
-
1
option :filter_log_class, optional: true
-
-
1
def initialize(...)
-
super
-
# Store grouped_logs if passed directly (for paginated requests)
-
then: 0
else: 0
@grouped_logs_value = @grouped_logs if @grouped_logs.present?
-
end
-
-
1
def grouped_logs
-
@grouped_logs ||= if @grouped_logs_value.present?
-
then: 0
# For paginated requests, use the passed grouped_logs
-
@grouped_logs_value
-
else
-
else: 0
# For initial load, limit logs but keep original group structure
-
grouped_logs_source = all_grouped_logs
-
per_page_count = per_page || PER_PAGE
-
logs_count = 0
-
limited_groups = []
-
-
grouped_logs_source.each do |group|
-
then: 0
else: 0
break if logs_count >= per_page_count
-
-
group_logs = group[:logs] || group["logs"] || []
-
remaining_slots = per_page_count - logs_count
-
-
if group_logs.count <= remaining_slots
-
then: 0
# Include entire group
-
limited_groups << group.dup
-
logs_count += group_logs.count
-
else
-
else: 0
# Include only part of the group
-
limited_groups << {
-
date: group[:date] || group["date"],
-
logs: group_logs.slice(0, remaining_slots),
-
}
-
logs_count += remaining_slots
-
end
-
end
-
-
limited_groups
-
end
-
end
-
-
1
def has_more
-
then: 0
@has_more ||= if @has_more_value.present?
-
@has_more_value
-
else: 0
else
-
(per_page || PER_PAGE) < all_logs.count
-
end
-
end
-
-
1
def logs_url
-
url_params = {
-
token: item.token,
-
page: (page || 1) + 1,
-
per_page: per_page || PER_PAGE,
-
}
-
-
# Add filter params if present
-
then: 0
else: 0
url_params[:filter_block_id] = filter_block_id if filter_block_id.present?
-
then: 0
else: 0
url_params[:filter_user_type] = filter_user_type if filter_user_type.present?
-
then: 0
else: 0
url_params[:filter_date_range] = filter_date_range if filter_date_range.present?
-
then: 0
else: 0
url_params[:filter_log_class] = filter_log_class if filter_log_class.present?
-
-
# Preserve source resolution across lazy-load requests.
-
then: 0
else: 0
if item.source.respond_to?(:source_context)
-
url_params[:context] = item.source.source_context
-
end
-
-
helpers.loopos_ui.logs_item_path(url_params)
-
end
-
-
1
def logs_filter_url
-
url_params = {
-
token: item.token,
-
}
-
-
# Preserve source resolution across filter requests.
-
then: 0
else: 0
if item.source.respond_to?(:source_context)
-
url_params[:context] = item.source.source_context
-
end
-
-
helpers.loopos_ui.logs_item_path(url_params)
-
end
-
-
1
def block_filter_options
-
@block_filter_options ||= begin
-
# Extract unique blocks from logs (using block_id as key, block name as value)
-
blocks_by_id = {}
-
all_logs.each do |log|
-
block_id_raw = log.dig("extra_data", "block_id") || log.dig(:extra_data, :block_id)
-
# block_id can be an array, so get the last one (current block)
-
then: 0
else: 0
block_id = block_id_raw.is_a?(Array) ? block_id_raw.last : block_id_raw
-
then: 0
else: 0
next if block_id.blank?
-
-
block = log["block"] || log[:block]
-
then: 0
else: 0
then: 0
else: 0
block_name = block&.dig("name") || block&.dig(:name)
-
-
# Only add if we have both block_id and block_name
-
then: 0
else: 0
if block_name.present?
-
blocks_by_id[block_id.to_s] = block_name
-
end
-
end
-
-
then: 0
if blocks_by_id.empty?
-
[]
-
else
-
else: 0
# Build options with block names (the name that appears in the log)
-
blocks_by_id.map do |block_id, block_name|
-
{
-
value: block_id,
-
text: block_name,
-
}
-
end
-
end
-
end
-
end
-
-
1
def user_filter_options
-
@user_filter_options ||= begin
-
# Extract unique users from logs
-
# Note: logs from API have "author" field (from jbuilder), but the raw log hash has "user"
-
# Logs come as hash with symbol keys like {:author=>"tech@theloop.pt"}
-
users = []
-
-
all_logs.each do |log|
-
then: 0
else: 0
log_hash = log.is_a?(Hash) ? log.with_indifferent_access : log
-
# Check both string and symbol keys, and both "user" and "author" fields
-
user_value = log_hash["user"] || log_hash[:user] || log_hash["author"] || log_hash[:author]
-
then: 0
else: 0
if user_value.present? && user_value.to_s.strip.present?
-
users << user_value.to_s.strip
-
end
-
end
-
-
options = []
-
# Add options for each unique user (using the actual user name/email)
-
users.uniq.sort.each do |user|
-
options << { value: user, text: user }
-
end
-
# Add system option if there are logs without users
-
has_system = all_logs.any? do |log|
-
then: 0
else: 0
log_hash = log.is_a?(Hash) ? log.with_indifferent_access : log
-
user_value = log_hash["user"] || log_hash[:user] || log_hash["author"] || log_hash[:author]
-
user_value.blank? || user_value.to_s.strip.blank?
-
end
-
then: 0
else: 0
options << { value: "system", text: I18n.t("admin.items.logs.filters.system_option") } if has_system
-
-
options
-
end
-
end
-
-
1
def log_class_filter_options
-
@log_class_filter_options ||= begin
-
log_classes = []
-
-
all_logs.each do |log|
-
then: 0
else: 0
log_hash = log.is_a?(Hash) ? log.with_indifferent_access : log
-
log_class_value = log_hash["log_class"] || log_hash[:log_class]
-
-
then: 0
else: 0
if log_class_value.present? && log_class_value.to_s.strip.present?
-
log_classes << log_class_value.to_s.strip
-
end
-
end
-
-
log_classes.uniq.sort.map do |log_class|
-
{
-
value: log_class,
-
text: I18n.t("admin.items.logs.log_classes.#{log_class}", default: log_class.to_s.humanize),
-
}
-
end
-
end
-
end
-
-
1
private
-
-
1
def all_grouped_logs
-
@all_grouped_logs ||= @item.grouped_logs
-
end
-
-
1
def all_logs
-
@all_logs ||= all_grouped_logs.flat_map { |group| group[:logs] || group["logs"] || [] }
-
end
-
end
-
end
-
end
-
end
-
<%= tag.turbo_frame id: "lui-tab-logs" do %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.logs.title"), description: I18n.t("admin.items.logs.tooltip"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(size: "small") %>
-
<% form_id = "logs_filter_form" %>
-
<%= form_with url: logs_filter_url, method: :get, id: form_id, data: { turbo_frame: "logs_list_frame" } do |form| %>
-
then: 0
else: 0
<% if item.source.respond_to?(:source_context) %>
-
<%= form.hidden_field :context, value: item.source.source_context %>
-
<% end %>
-
<%= tag.div class: "lui-logs-filters" do %>
-
<%= tag.div class: "lui-logs-filters__field--date" do %>
-
<%= render LooposUi::DatePicker.new(
-
range: true,
-
format: "date",
-
locale: I18n.locale.to_s,
-
name: "filter_date_range",
-
end_date: Date.current,
-
submit_on_select: true
-
) %>
-
<% end %>
-
then: 0
else: 0
<% if block_filter_options.any? %>
-
<%= tag.div class: "lui-logs-filters__field" do %>
-
<%= render LooposUi::Inputs::Select2.new(
-
name: "filter_block_id",
-
options: block_filter_options,
-
value: filter_block_id,
-
placeholder: I18n.t("admin.items.logs.filters.block_placeholder"),
-
mode: :form,
-
multiple: true,
-
show_actions_in_menu: true,
-
show_loading_on_submit: false,
-
form: form_id
-
) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if user_filter_options.any? %>
-
<%= tag.div class: "lui-logs-filters__field" do %>
-
<%= render LooposUi::Inputs::Select2.new(
-
name: "filter_user_type",
-
options: user_filter_options,
-
value: filter_user_type,
-
placeholder: I18n.t("admin.items.logs.filters.user_placeholder"),
-
mode: :form,
-
multiple: true,
-
show_actions_in_menu: true,
-
show_loading_on_submit: false,
-
form: form_id
-
) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if log_class_filter_options.any? %>
-
<%= tag.div class: "lui-logs-filters__field" do %>
-
<%= render LooposUi::Inputs::Select2.new(
-
name: "filter_log_class",
-
options: log_class_filter_options,
-
value: filter_log_class,
-
placeholder: I18n.t("admin.items.logs.filters.log_class_placeholder"),
-
searchable: false,
-
mode: :form,
-
multiple: true,
-
show_actions_in_menu: true,
-
show_loading_on_submit: false,
-
form: form_id
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.turbo_frame id: "logs_list_frame", class: "lui-logs-frame" do %>
-
<%= tag.div class: "lui-logs-frame__form-loader" do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<%= tag.div \
-
class: "lui-lazy-load-logs lui-logs-frame__content",
-
data: {
-
controller: "lui--lazy-load-logs",
-
then: 0
else: 0
"lui--lazy-load-logs-url-value": (logs_url if has_more),
-
"lui--lazy-load-logs-has-more-value": has_more
-
}.compact do %>
-
<%= render LooposUi::LogList.from_any_source(
-
grouped_logs,
-
id: "logs",
-
config: { has_pagination: false, max_items: [grouped_logs.flat_map { |g| (g[:logs] || g["logs"] || []) }.count, 1].max },
-
)
-
%>
-
<%= tag.div \
-
class: "lui-lazy-load-logs__loader",
-
data: { "lui--lazy-load-logs-target": "loader" },
-
style: "display: none;" do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_logs_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Notes < LoopComponent
-
1
KEY = :item_notes
-
-
1
option :item
-
1
then: 0
else: 0
delegate :source, to: :@item
-
-
1
def notes
-
-
item.notes(app: LooposUi.config.app_type)
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.item_notes.title"), description: I18n.t("admin.items.item_notes.description"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= form_with url: helpers.loopos_ui.update_notes_item_path(token: item.token, context: source.source_context), method: :patch do |form| %>
-
<%= render LooposUi::Inputs::RichText.new(
-
name: "content",
-
placeholder: I18n.t("admin.items.item_notes.placeholder"),
-
value: notes,
-
mode: :autosubmit,
-
upload_endpoint: helpers.loopos_ui.upload_attachment_item_path(token: item.token, context: source.source_context, authenticity_token: form_authenticity_token),
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_item_notes_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class ProtocolResponses < LoopComponent
-
1
KEY = :protocol_answers
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
-
1
def partial_exists?(partial)
-
# template exists expects the partial name with a _ prefix, add that
-
add_underscore_regex = %r{(?:/)(?!_)([^/]+)$}
-
lookup_context.template_exists?(partial.sub(add_underscore_regex, '/_\1'), formats: [:json, :html])
-
end
-
-
1
def submission_extra_arrow_svg
-
'<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-
<rect x="0.5" y="0.5" width="13" height="13" rx="6.5" fill="#F8ECE6"/>
-
<rect x="0.5" y="0.5" width="13" height="13" rx="6.5" stroke="white"/>
-
<path d="M10.3438 7.35938L7.84375 9.85938C7.65625 10.0625 7.32812 10.0625 7.14062 9.85938C6.9375 9.67188 6.9375 9.34375 7.14062 9.15625L8.78125 7.5H4C3.71875 7.5 3.5 7.28125 3.5 7C3.5 6.73438 3.71875 6.5 4 6.5H8.78125L7.14062 4.85938C6.9375 4.67188 6.9375 4.34375 7.14062 4.15625C7.32812 3.95312 7.65625 3.95312 7.84375 4.15625L10.3438 6.65625C10.5469 6.84375 10.5469 7.17188 10.3438 7.35938Z" fill="#B53C00"/>
-
</svg>'.html_safe
-
end
-
-
1
class ProtocolAnswerValue < LoopComponent
-
1
option :kind, types: Types::Coercible::Symbol.enum(["text", "bool", "date", "files", "images", "number", "select"]), optional: true
-
1
option :value, optional: true
-
-
1
def value_mapped
-
then: 0
if value.is_a?(Array)
-
then: 0
else: 0
value.map { |v| v.respond_to?(:with_indifferent_access) ? v.with_indifferent_access : v }
-
else: 0
else
-
[]
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
<% case kind.to_s %>
-
when: 0
<% when "bool", "date", "number", "select" %>
-
then: 0
<% if value.present? %>
-
<%= render(LooposUi::Token.new(text: value.to_s)) %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
when: 0
<% when "files" %>
-
<%= react_component("FilesGallery", { files: value_mapped.pluck(:url), size: "small", railsIcon: true }) %>
-
when: 0
<% when "images" %>
-
<%= react_component("ImagesGallery", { images: value_mapped.pluck(:url), size: "small" }) %>
-
when: 0
<% when "text" %>
-
<%== (value.to_s.gsub("\\n", "\n").presence || "-").gsub(/\r\n?|\n/, "<br>") %>
-
else: 0
<% else %>
-
then: 0
<% if value.present? %>
-
<%= render(LooposUi::Token.new(text: value.to_s)) %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
<% end %>
-
<%
-
header_steps = ["submission", "validation", "hubs", "handling", "submission_extra"]
-
columns = [
-
{ title: I18n.t("admin.items.protocol_answers.table.question"), sortable: false, dataIndex: "question", key: "question", type: "html" }
-
]
-
-
columns_titles = header_steps.map do |step|
-
content_tag(:div, class: "protocol-answer__header", id: "tooltip_#{step}") do
-
concat(
-
content_tag(:div, class: "protocol-answer__image") do
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
content_tag(:div, class: "protocol-answer__wrapper-image #{item.protocol_answers&.any? { |pa| pa["answers"]&.any? { |a| a["step"] == step } } ? "" : "protocol-answer__wrapper-image__inactive"}") do
-
then: 0
concat(if step == "submission_extra"
-
content_tag(:div, class:"relative inline-block") do
-
render(LooposUi::Logo.new(app: "submission", size: "small", icon: true, count: nil)) +
-
content_tag(:div, class: "absolute -top-2 -right-2") do
-
raw(submission_extra_arrow_svg)
-
end
-
end
-
else: 0
else
-
render LooposUi::Logo.new(app: step, size: "small", icon: true, count: nil )
-
end)
-
concat(render LooposUi::Tooltip.new(title: "LoopOs #{step.capitalize}", tippy_target_id: "tooltip_#{step}", position: :bottom))
-
end
-
end
-
)
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
answer = item.protocol_answers&.find { |pa| pa["answers"]&.find { |a| a["step"] == step } }&.dig("answers")&.find { |a| a["step"] == step }
-
-
then: 0
else: 0
if answer.present?
-
concat(
-
content_tag(:div, class: "protocol-answer__specs") do
-
then: 0
else: 0
concat content_tag(:p, answer["created_at"].present? ? (Date.strptime(answer["created_at"], "%d/%m/%Y").strftime("%d %h %Y") rescue answer["created_at"]) : "", class: "protocol-answer__date")
-
concat content_tag(:p, answer["created_by"], class: "protocol-answer__user")
-
end
-
)
-
end
-
end
-
end
-
-
columns += columns_titles.map.with_index do |title_content, index|
-
{
-
title: capture { title_content },
-
sortable: false,
-
dataIndex: header_steps[index],
-
key: header_steps[index],
-
type: "html"
-
}
-
end
-
%>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.protocol_answers.title"), description: I18n.t("admin.items.protocol_answers.label"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<% header_steps = ["submission", "validation", "hubs", "handling", "submission_extra"] %>
-
then: 0
else: 0
then: 0
<% if item.protocol_answers&.any? %>
-
<%= render LooposUi::V2::Table.new(pagy: @pagy, columns: columns, searchable: false, show_result_count: false) do |table| %>
-
then: 0
else: 0
<% item.protocol_answers&.group_by { |pa| pa["protocol_element"] }.each_with_index do |hash, index| %>
-
<%
-
element = hash.first
-
answers = hash.second
-
%>
-
<% table.with_row(key: element["id"]) do |row| %>
-
<% row.with_cell(property: :question) do %>
-
<div class="protocol-answer__label">
-
<%= tag.p element["label"] %>
-
</div>
-
<% end %>
-
<% header_steps.each do |step| %>
-
then: 0
else: 0
then: 0
else: 0
<% answer = answers&.dig(0, "answers")&.find { |a| a["step"] == step } %>
-
<% row.with_cell(property: step) do %>
-
then: 0
<% if answer.present? %>
-
<div class="protocol-answer__label">
-
then: 0
else: 0
<%= render LooposUi::PageSection::ItemTab::ProtocolResponses::ProtocolAnswerValue.new(kind: answer["type"]&.to_sym, value: answer["value"]) %>
-
</div>
-
else: 0
<% else %>
-
<div class="protocol-answer__label">-</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::TabsSection.new(description: I18n.t("admin.items.protocol_answers.item_with_no_answers"), size: "small" ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_protocol_answers_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Rfi < LoopComponent
-
1
KEY = :request_for_information
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
-
1
def rfis
-
data = item.source.rfis(item.token)
-
@pagy = data[:pagy]
-
data[:data]
-
end
-
-
1
def rfi_answers(rfi_id)
-
item.source.rfi_answers(rfi_id).values_at(:data, :pagy)
-
end
-
-
1
def rfi_final_choice(final_choice_id)
-
else: 0
then: 0
return unless final_choice_id.present?
-
-
PageSources::Models::RfiAnswers.find_by(rfi_answer_id: final_choice_id, source: item.source)
-
end
-
-
1
def fetch_url(rfi_id)
-
helpers.loopos_ui.rfi_answers_rfi_url(
-
rfi_id: rfi_id,
-
item_token: item.token,
-
with_user_info: false,
-
context: item.source.source_context,
-
)
-
end
-
-
1
def history_urls(rfi_id, rfi_answers)
-
rfi_answers.each_with_object({}) do |rfi_answer, hash|
-
hash[rfi_answer.id] = rfi_answer.history_url(item.token)
-
end
-
end
-
-
1
class RfiAnswersIndexTable < LoopComponent
-
1
option :rfi_id
-
1
option :rfi_answers
-
1
option :fetch_url
-
1
option :pagy
-
1
option :history_urls
-
end
-
-
1
class RfiAnswersStepTable < LoopComponent
-
1
option :rfi_id
-
1
option :rfi_answers
-
1
option :presenter
-
1
option :fetch_url
-
1
option :pagy
-
end
-
-
1
class RfiAnswerHistoryTable < LoopComponent
-
1
include LooposUi::Concerns::ProtocolAnswerValueFormatter
-
-
1
option :rfi_answer_id
-
1
option :rfi_id
-
1
option :rfi_answers
-
1
option :fetch_url
-
1
option :pagy
-
1
option :columns_protocol_answers
-
end
-
-
1
class RfiRespondersWithoutRfiAnswersTable < LoopComponent
-
1
option :rfi_id
-
1
option :responders_without_rfi_answers
-
1
option :presenter
-
1
option :fetch_url
-
1
option :pagy
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<% rfis.each do |rfi| %>
-
<% rfi_final_choice = rfi_final_choice(rfi.final_choice_id) %>
-
then: 0
else: 0
<% if rfi_final_choice.present? %>
-
<%= render "loopos_ui/rfi/final_choice_card", rfi_name: rfi.name, rfi_final_choice: rfi_final_choice, hide_rfi_identification: false %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<% rfis.each do |rfi| %>
-
<%= render LooposUi::Accordion.new(open: true) do |accordion| %>
-
<% accordion.with_header(title: rfi.name, size: :normal) %>
-
<%= tag.turbo_frame id: "rfi_answers_index_table_#{rfi.id}", src: helpers.loopos_ui.rfi_answers_rfi_url(rfi_id: rfi.id, item_token: item.token, context: item.source.source_context) do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<% accordion.with_action_buttons do |ab| %>
-
<% ab.with_button_group do |g| %>
-
<% g.with_button(
-
text: I18n.t("admin.items.rfi.open_rfi"),
-
size: :tiny,
-
type: :secondary,
-
kind: :neutral,
-
leading_icon: "open_in_new",
-
disabled: rfi.show_url(item).blank?,
-
href: rfi.show_url(item),
-
tag_options: {
-
data: {
-
"inline-edit-target": "submit",
-
turbo_frame: 'lui-main-layout',
-
"turbo-action": "advance"
-
}
-
}
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_rfi_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%
-
columns = [
-
{ title: "ID", dataIndex: "id", key: "id", default_sort: :asc, fixed: "left"},
-
{ title: I18n.t("admin.items.rfi_answers.responder"), dataIndex: "responder", key: "responder"},
-
{ title: I18n.t("admin.items.rfi_answers.answer_by"), dataIndex: "answered_by", key: "answered_by"},
-
{ title: I18n.t("admin.items.rfi_answers.status"), dataIndex: "state", key: "state" },
-
{ title: I18n.t("admin.items.rfi_answers.email"), dataIndex: "email", key: "email" },
-
{ title: I18n.t("admin.items.rfi_answers.phone"), dataIndex: "phone", key: "phone" },
-
{ title: I18n.t("admin.items.rfi_answers.distance"), dataIndex: "distance", key: "distance" }
-
]
-
-
then: 0
else: 0
columns += columns_protocol_answers if columns_protocol_answers.any?
-
columns.concat([
-
# { title: I18n.t("admin.items.rfi_answers.notes"), dataIndex: "notes", key: "notes", max_width: "200px" },
-
])
-
pagination = {
-
then: 0
else: 0
page: pagy&.page || 1,
-
then: 0
else: 0
per_page: pagy&.items || 15,
-
then: 0
else: 0
total: pagy&.count || rfi_answers.size,
-
}
-
options = {}
-
options.merge!({
-
id: rfi_id,
-
pagination: pagination,
-
columns: columns,
-
searchable: false,
-
selectable: false,
-
pagy: pagy,
-
fetch_url: fetch_url,
-
})
-
%>
-
-
<%= helpers.turbo_frame_tag "rfi_history_answers_content_#{rfi_answer_id}" do %>
-
<%= render LooposUi::V2::Table.new(**options) do |table| %>
-
<% rfi_answers.each do |rfi_answer| %>
-
<% table.with_row(key: rfi_answer.id) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
<%= rfi_answer.id_label %>
-
<% end %>
-
<% row.with_cell(property: :responder) do %>
-
then: 0
<% if rfi_answer.responder_name.present? %>
-
<%= render LooposUi::Label.new(text: rfi_answer.responder_name, icon: rfi_answer.responder_logo) %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :answered_by) do %>
-
then: 0
<% if rfi_answer.answered_by_name.present? %>
-
<%= render LooposUi::Label.new(text: rfi_answer.answered_by_name, icon: rfi_answer.answered_by_logo) %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :email) do %>
-
<%= rfi_answer.email.presence || "-" %>
-
<% end %>
-
<% row.with_cell(property: :phone) do %>
-
<%= rfi_answer.phone.presence || "-" %>
-
<% end %>
-
<% row.with_cell(property: :distance) do %>
-
then: 0
<% if rfi_answer.distance.present? %>
-
<%= "#{rfi_answer.distance.round(2)} km" %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
then: 0
<% if columns_protocol_answers.any? && rfi_answer.protocol_answers.any? %>
-
<% rfi_answer.protocol_answers.each do |protocol_answer| %>
-
then: 0
else: 0
<% row.with_cell(property: "protocol_answers.#{protocol_answer.respond_to?(:protocol_element) ? protocol_answer.protocol_element.id : protocol_answer.dig(:protocol_element, :id)}") do %>
-
<%= protocol_answer_value(protocol_answer, model: protocol_answer.respond_to?(:protocol_element)) || "-" %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<% columns.each do |column| %>
-
then: 0
else: 0
<% if column[:dataIndex].include?("protocol_answers.") %>
-
<% row.with_cell(property: column[:dataIndex]) do %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :state) do %>
-
<%= render LooposUi::StateLabel.new(
-
text: I18n.t("admin.items.rfi_answers.statuses.#{rfi_answer.status}", default: rfi_answer.status.titleize),
-
color: LooposUi::Resources::RfiAnswerResource::RfiAnswer::STATUS_LABEL_MAPPING.fetch(rfi_answer.status.to_sym, :neutral)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%
-
columns = [
-
{ title: "ID", dataIndex: "id", key: "id", fixed: "left"},
-
{ title: I18n.t("admin.items.rfi_answers.responder"), dataIndex: "responder", key: "responder"},
-
{ title: I18n.t("admin.items.rfi_answers.status"), dataIndex: "status", key: "status" },
-
{ title: I18n.t("admin.items.rfi_answers.answer_by"), dataIndex: "answer_by", key: "answer_by"},
-
{ title: I18n.t("admin.items.rfi_answers.email"), dataIndex: "email", key: "email" },
-
{ title: I18n.t("admin.items.rfi_answers.phone"), dataIndex: "phone", key: "phone" },
-
{ title: I18n.t("admin.items.rfi_answers.distance"), dataIndex: "distance", key: "distance" },
-
{ dataIndex: "actions", key: "actions"},
-
]
-
-
pagination = {
-
then: 0
else: 0
page: pagy&.page || 1,
-
then: 0
else: 0
per_page: pagy&.items || 15,
-
total: rfi_answers.size
-
}
-
options = {}
-
options.merge!({
-
id: rfi_id,
-
pagination: pagination,
-
columns: columns,
-
fetch_url: fetch_url,
-
pagy: pagy,
-
show_result_count: false,
-
})
-
%>
-
-
<%= tag.turbo_frame id: "rfi_answers_index_table_#{rfi_id}" do %>
-
<%= render LooposUi::V2::Table.new(**options) do |table| %>
-
<% rfi_answers.each do |rfi_answer| %>
-
<% table.with_row(key: rfi_answer.id) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
then: 0
<% if rfi_answer.show_url.present? %>
-
<%= link_to rfi_answer.show_url, data: { turbo_frame: "lui-main-layout" } do %>
-
<%= rfi_answer.id_label %>
-
<% end %>
-
else: 0
<% else %>
-
<%= rfi_answer.id_label %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :responder) do %>
-
<%= render LooposUi::Label.new(text: rfi_answer.responder_name, icon: rfi_answer.responder_logo) %>
-
<% end %>
-
<% row.with_cell(property: :answer_by) do %>
-
then: 0
<% if rfi_answer.answered_by_name.present? %>
-
<%= render LooposUi::Label.new(text: rfi_answer.answered_by_name, icon: rfi_answer.answered_by_logo) %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :email) do %>
-
<%= rfi_answer.email %>
-
<% end %>
-
<% row.with_cell(property: :phone) do %>
-
<%= rfi_answer.phone %>
-
<% end %>
-
<% row.with_cell(property: :distance) do %>
-
then: 0
<% if rfi_answer.distance.present? %>
-
<%= "#{rfi_answer.distance} km" %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :status) do %>
-
<%= render LooposUi::StateLabel.new(
-
text: I18n.t("admin.items.rfi_answers.statuses.#{rfi_answer.status}", default: rfi_answer.status.titleize),
-
color: LooposUi::Resources::RfiAnswerResource::RfiAnswer::STATUS_LABEL_MAPPING.fetch(rfi_answer.status.to_sym, :neutral)) %>
-
<% end %>
-
<% row.with_cell(property: :actions) do %>
-
<div class="flex p-[5px]">
-
<%= render LooposUi::Modal.new(
-
id: "rfi_answer_history_#{rfi_answer.id}",
-
title: I18n.t("admin.items.rfi_answers.history_modal_title"),
-
show_footer: false,
-
modal_width: 1000,
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
type: :secondary,
-
size: :tiny,
-
text: I18n.t("admin.items.rfi_answers.history_modal_title"),
-
disabled: !rfi_answer.has_history?,
-
tag_options: {
-
id: "rfi_answer_history_button_#{rfi_answer.id}",
-
data: {
-
controller: "rfi-history-answers",
-
action: "click->modal#open click->rfi-history-answers#load",
-
lock_disabled: true,
-
disabled: !rfi_answer.has_history?,
-
modal_id: "rfi_answer_history_#{rfi_answer.id}",
-
history_url: history_urls[rfi_answer.id],
-
frame_id: "rfi_history_answers_content_#{rfi_answer.id}"
-
}
-
}
-
) %>
-
<% end %>
-
-
<% modal.with_custom_content do %>
-
then: 0
else: 0
<% if rfi_answer.has_history? %>
-
<%= helpers.turbo_frame_tag "rfi_history_answers_content_#{rfi_answer.id}" do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%
-
columns = [
-
{ title: "ID", dataIndex: "id", key: "id", fixed: "left"},
-
{ title: I18n.t("admin.items.rfi_answers.responder"), dataIndex: "responder", key: "responder"},
-
{ title: I18n.t("admin.items.rfi_answers.distance"), dataIndex: "distance", key: "distance" },
-
]
-
columns_protocol_answers = presenter.columns_protocol_answers(rfi_answers)
-
then: 0
else: 0
columns += columns_protocol_answers if columns_protocol_answers.any?
-
columns.concat([
-
{ dataIndex: "actions", key: "actions", fixed: "right", className: "lui-table__cell--align-right" },
-
])
-
pagination = {
-
then: 0
else: 0
page: pagy&.page || 1,
-
then: 0
else: 0
per_page: pagy&.items || 15,
-
then: 0
else: 0
total: pagy&.count || rfi_answers.size,
-
}
-
options = {}
-
options.merge!({
-
id: "rfi_answers_step_table_#{rfi_id}",
-
pagination: pagination,
-
columns: columns,
-
searchable: false,
-
selectable: presenter.can_approve_rfi_answer?,
-
selectable_type: :radio,
-
pagy: pagy,
-
fetch_url: fetch_url,
-
show_result_count: false,
-
})
-
%>
-
<div data-controller="rfi-answers-table" >
-
-
<%= render LooposUi::V2::Table.new(**options) do |table| %>
-
<% rfi_answers.each do |rfi_answer| %>
-
<% table.with_row(key: rfi_answer.dig(:id), row_data: { selection_disabled: presenter.can_requote_rfi_answer?(rfi_answer) }) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
<%= rfi_answer.dig(:id_label) %>
-
<% end %>
-
<% row.with_cell(property: :responder) do %>
-
<%= render LooposUi::Label.new(text: rfi_answer.dig(:responder, :name), icon: rfi_answer.dig(:responder, :logo)) %>
-
<% end %>
-
<% row.with_cell(property: :distance) do %>
-
then: 0
<% if rfi_answer.dig(:distance).present? %>
-
then: 0
else: 0
<%= "#{rfi_answer.dig(:distance)&.round(2)} km" %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
then: 0
<% if columns_protocol_answers.any? && rfi_answer.dig(:protocol_answers).any? %>
-
<% rfi_answer.dig(:protocol_answers).each do |protocol_answer| %>
-
<% row.with_cell(property: "protocol_answers.#{protocol_answer.dig(:protocol_element, :id)}") do %>
-
then: 0
else: 0
<%= presenter&.protocol_answer_value(protocol_answer) || "-" %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<% columns.each do |column| %>
-
then: 0
else: 0
<% if column[:dataIndex].include?("protocol_answers.") %>
-
<% row.with_cell(property: column[:dataIndex]) do %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :actions) do %>
-
then: 0
else: 0
<% if presenter.can_approve_rfi_answer? %>
-
<div class="flex items-end justify-end gap-2 p-[5px]">
-
<%= form_with url:helpers.loopos_ui.stepper_path(step: :evaluate_rfi_answers, token: presenter.item_token), method: :patch, local: false, class: "inline-flex items-center", data: { turbo_stream: true } do |form| %>
-
<%= form.hidden_field :step_action, value: :requote_rfi_answer %>
-
<%= form.hidden_field :rfi_answer_id, value: rfi_answer.dig(:id) %>
-
<%= form.hidden_field :rfi_id, value: rfi_id %>
-
<div id="requote_rfi_button_<%= rfi_answer.dig(:id) %>">
-
<%= render LooposUi::Button.new(
-
leading_icon: :question_mark,
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
tooltip_text: I18n.t("admin.items.rfi_answers.requote_tooltip"),
-
disabled: presenter.can_requote_rfi_answer?(rfi_answer),
-
tag_options: {
-
type: :submit
-
}
-
) %>
-
</div>
-
<% end %>
-
<%= render LooposUi::Modal.new(title: I18n.t("admin.items.rfi_answers.reject_modal.title"), form_id: "reject_rfi_answer_form_#{rfi_answer.dig(:id)}") do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
icon: "fa-regular fa-xmark",
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
tooltip_text: I18n.t("admin.items.rfi_answers.reject_tooltip"),
-
) %>
-
<% end %>
-
<span style="white-space:normal; text-align: left;">
-
<%= I18n.t("admin.items.rfi_answers.reject_modal.description", responder: rfi_answer.dig(:responder, :name)) %>
-
</span>
-
<%= form_with id: "reject_rfi_answer_form_#{rfi_answer.dig(:id)}", url: helpers.loopos_ui.stepper_path(step: :evaluate_rfi_answers, token: presenter.item_token), method: :patch, local: false, class: "inline", data: { turbo_stream: true } do |form| %>
-
<%= form.hidden_field :step_action, value: :reject_rfi_answer %>
-
<%= form.hidden_field :rfi_answer_id, value: rfi_answer.dig(:id) %>
-
<%= form.hidden_field :rfi_id, value: rfi_id %>
-
<% end %>
-
<% modal.with_primary_action(text: I18n.t("admin.items.rfi_answers.reject_modal.confirm_button"), tag_options: {
-
type: :submit,
-
text: I18n.t("admin.items.rfi_answers.reject_modal.confirm_button"),
-
})
-
%>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if presenter.can_add_manual_rfi_responses? %>
-
<%= table.with_footer(class: "w-full flex justify-between") do %>
-
<%= form_with url: helpers.loopos_ui.stepper_path(step: :evaluate_rfi_answers, token: presenter.item_token), method: :patch, local: false, class: "inline", data: { turbo_stream: true } do |form| %>
-
<%= form.hidden_field :step_action, value: :add_manual_answer %>
-
<%= form.hidden_field :rfi_answer_id, value: nil %>
-
<%= form.hidden_field :rfi_id, value: rfi_id %>
-
<%= render LooposUi::Button.new(
-
text: I18n.t("admin.items.rfi_answers.add_manual_answer"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :tiny,
-
icon: "fa-regular fa-plus",
-
tag_options: {
-
type: :submit
-
}
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
</div>
-
<%
-
columns = [
-
{ title: I18n.t("admin.items.rfi_answers.responder"), dataIndex: "responder", key: "responder", fixed: "left"},
-
{ title: I18n.t("admin.items.rfi_answers.email"), dataIndex: "email", key: "email"},
-
{ title: I18n.t("admin.items.rfi_answers.phone"), dataIndex: "phone", key: "phone"},
-
{ title: I18n.t("admin.items.rfi_answers.distance"), dataIndex: "distance", key: "distance" }
-
]
-
pagination = {
-
then: 0
else: 0
page: pagy&.page || 1,
-
then: 0
else: 0
per_page: pagy&.items || 15,
-
then: 0
else: 0
total: pagy&.count || responders_without_rfi_answers.size,
-
}
-
-
table_id = "responders_without_rfi_answers_table_#{rfi_id}"
-
button_id = "request_button_responders_without_rfi_answers_#{rfi_id}"
-
-
options = {}
-
options.merge!({
-
id: table_id,
-
pagination: pagination,
-
columns: columns,
-
selectable: presenter.can_add_rfi_responders?,
-
selectable_type: :radio,
-
pagy: pagy,
-
fetch_url: fetch_url,
-
show_result_count: false
-
})
-
%>
-
-
<%= helpers.turbo_frame_tag "rfi_responders_without_rfi_answers_#{rfi_id}" do %>
-
<%= render LooposUi::V2::Table.new(**options) do |table| %>
-
<% responders_without_rfi_answers.each do |responder| %>
-
<% table.with_row(key: responder.dig(:id), row_data: { point: responder.dig(:point), app_instance_id: responder.dig(:app_instance_id) }) do |row| %>
-
<% row.with_cell(property: :responder) do %>
-
<%= render LooposUi::Label.new(text: responder.dig(:name), icon: responder.dig(:logo)) %>
-
<% end %>
-
<% row.with_cell(property: :email) do %>
-
<%= responder.dig(:email) %>
-
<% end %>
-
<% row.with_cell(property: :phone) do %>
-
<%= responder.dig(:phone) %>
-
<% end %>
-
<% row.with_cell(property: :distance) do %>
-
then: 0
<% if responder.dig(:distance).present? %>
-
then: 0
else: 0
<%= "#{responder.dig(:distance)&.round(2)} km" %>
-
else: 0
<% else %>
-
<%= "-" %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if presenter.can_add_rfi_responders? %>
-
<div class="mt-2">
-
<%= form_with url:helpers.loopos_ui.stepper_path(step: :evaluate_rfi_answers, token: presenter.item_token), method: :patch, local: false, class: "inline", data: { turbo_stream: true } do |form| %>
-
<%= form.hidden_field :step_action, value: :create_rfi_answer_for_responder %>
-
<%= form.hidden_field :rfi_id, value: rfi_id %>
-
<%= form.hidden_field :responder_id, value: nil %>
-
<%= form.hidden_field :point, value: nil %>
-
<%= form.hidden_field :app_instance_id, value: nil %>
-
<%= render LooposUi::Button.new(
-
kind: :neutral,
-
type: :primary,
-
size: :tiny,
-
full: true,
-
text: I18n.t("admin.items.rfi_answers.request"),
-
disabled: true,
-
tag_options: {
-
id: button_id,
-
type: :submit,
-
data: {
-
table_id: table_id
-
}
-
}
-
)%>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class Services < LoopComponent
-
1
KEY = :services
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
-
1
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
delegate :shippings, :emails, :incoming_payments, :invoices, :payments, :sms_messages, to: :@item
-
-
1
def permissions
-
# TODO
-
{ can_view_errors: true, can_view_providers: true, can_view_templates: true }
-
end
-
-
1
private
-
-
# TODO: This methods wouldnt be needed if the presenter were setup in the from_hash file
-
1
def transformed_emails
-
then: 0
if LooposUi.config.app_type?(:core)
-
emails
-
else: 0
else
-
Array(emails).map { |email| LooposUi::Services::EmailPresenter.new(email, item_data: item).serialize }
-
end
-
-
rescue => e
-
Rails.logger.error("Error transforming emails: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :emails }) if defined?(Sentry)
-
[]
-
end
-
-
1
def transformed_incoming_payments
-
then: 0
if LooposUi.config.app_type?(:core)
-
incoming_payments
-
else: 0
else
-
Array(incoming_payments).map { |incoming_payment| LooposUi::Services::IncomingPaymentPresenter.new(incoming_payment, item_data: item).serialize }
-
end
-
rescue => e
-
Rails.logger.error("Error transforming incoming payments: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :incoming_payments }) if defined?(Sentry)
-
[]
-
end
-
-
1
def transformed_invoices
-
then: 0
if LooposUi.config.app_type?(:core)
-
invoices
-
else: 0
else
-
Array(invoices).map { |invoice| LooposUi::Services::InvoicePresenter.new(invoice, item_data: item).serialize }
-
end
-
rescue => e
-
Rails.logger.error("Error transforming invoices: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :invoices }) if defined?(Sentry)
-
[]
-
end
-
-
1
def transformed_payments
-
payments
-
rescue => e
-
Rails.logger.error("Error transforming payments: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :payments }) if defined?(Sentry)
-
[]
-
end
-
-
1
def transformed_sms_messages
-
then: 0
if LooposUi.config.app_type?(:core)
-
sms_messages
-
else: 0
else
-
Array(sms_messages).map { |sms_message| LooposUi::Services::SmsMessagePresenter.new(sms_message, item_data: item).serialize }
-
end
-
rescue => e
-
Rails.logger.error("Error transforming sms messages: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :sms_messages }) if defined?(Sentry)
-
[]
-
end
-
-
1
def transformed_shippings
-
then: 0
if LooposUi.config.app_type?(:core)
-
shippings
-
else: 0
else
-
Array(shippings).map { |shipping| LooposUi::Services::ShippingPresenter.new(shipping, item_data: item).serialize }
-
end
-
rescue => e
-
Rails.logger.error("Error transforming shippings: #{e.message}")
-
then: 0
else: 0
Sentry.capture_exception(e, extra: { tab: :services, type: :shippings }) if defined?(Sentry)
-
[]
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new( title: I18n.t("admin.items.services.title"), description: I18n.t("admin.items.services.label"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.transportation"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_shippings" do %>
-
then: 0
<% if transformed_shippings.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::Shippings::Table.new(
-
id: "item_#{item.id}_shippings_table",
-
data: transformed_shippings,
-
pagination: { current: 1, pageSize: transformed_shippings.count, total: transformed_shippings.count },
-
permissions: permissions,
-
show_shipping_guide: true,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.email"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_emails" do %>
-
then: 0
<% if transformed_emails.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::EmailMessages::Table.new(
-
id: "item_#{item.id}_emails_table",
-
data: transformed_emails,
-
pagination: { current: 1, pageSize: transformed_emails.count, total: transformed_emails.count },
-
permissions: permissions,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.incoming_payments"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_incoming_payments" do %>
-
then: 0
<% if transformed_incoming_payments.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::IncomingPayments::Table.new(
-
id: "item_#{item.id}_incoming_payments_table",
-
data: transformed_incoming_payments,
-
pagination: { current: 1, pageSize: transformed_incoming_payments.count, total: transformed_incoming_payments.count },
-
permissions: permissions,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.outgoing_payments"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_payments" do %>
-
then: 0
<% if transformed_payments.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::Payments::Table.new(
-
id: "item_#{item.id}_payments_table",
-
data: transformed_payments,
-
pagination: { current: 1, pageSize: transformed_payments.count, total: transformed_payments.count },
-
permissions: permissions,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.invoices"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_invoices" do %>
-
then: 0
<% if transformed_invoices.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::Invoices::Table.new(
-
id: "item_#{item.id}_invoices_table",
-
data: transformed_invoices,
-
pagination: { current: 1, pageSize: transformed_invoices.count, total: transformed_invoices.count },
-
permissions: permissions,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.services.sms"), size: "small", underline: true) do %>
-
<%= tag.turbo_frame id: "item_#{item.id}_sms_messages" do %>
-
then: 0
<% if transformed_sms_messages.empty? %>
-
<%= render LooposUi::TitleDescription.new(description: I18n.t("admin.items.services.no_data")) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Services::SmsMessages::Table.new(
-
id: "item_#{item.id}_sms_messages_table",
-
data: transformed_sms_messages,
-
pagination: { current: 1, pageSize: transformed_sms_messages.count, total: transformed_sms_messages.count },
-
permissions: permissions,
-
searchable: false,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_services_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class TradeIn < LoopComponent
-
1
KEY = :trade_in
-
-
1
option :item, Types::Instance(PageSources::Models::Item)
-
-
1
def forward_item
-
then: 0
else: 0
item.forward_item.is_a?(Hash) ? item.forward_item : item.forward_item.info
-
end
-
-
1
def fti_product
-
forward_item.dig(:forward_product_name)
-
end
-
-
1
def fti_category
-
then: 0
else: 0
forward_item.dig(:forward_product_category)&.first
-
end
-
-
1
def fti_brand
-
forward_item.dig(:forward_product_brand)
-
end
-
-
1
def fti_variant
-
forward_item.dig(:forward_variant_name)
-
end
-
-
1
def fti_option_values
-
forward_item.dig(:forward_product_types_values)
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.trade_in.title"), description: I18n.t("admin.items.trade_in.label"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.trade_in.category"), required: false, orientation: "horizontal") do |entry| %>
-
<%= entry.with_input do %>
-
<%= render(LooposUi::Entities::Category.new(category: OpenStruct.new(name: fti_category))) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.trade_in.brand"), required: false, orientation: "horizontal") do |entry| %>
-
<%= entry.with_input do %>
-
<%= render(LooposUi::Entities::Brand.new(brand: OpenStruct.new(name: fti_brand))) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.trade_in.product"), required: false, orientation: "horizontal") do |entry| %>
-
<%= entry.with_input do %>
-
<%= render(LooposUi::Entities::Product.new(product: OpenStruct.new(name: fti_product))) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.trade_in.variant"), required: false, orientation: "horizontal") do |entry| %>
-
<%= entry.with_input do %>
-
<%= render(LooposUi::Token.new(text: fti_variant)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if fti_variant.present? %>
-
then: 0
else: 0
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.option_values"), required: false, orientation: "horizontal") do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
<% fti_option_values.each do |option| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::EntityToken.new(text: "#{option[:value]}") %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if fti_option_values.present? %>
-
<% item.fti_protocol_answers.each do |protocol_answer| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: protocol_answer.dig(:protocol_element, :label), required: false, orientation: "horizontal") do |entry| %>
-
<% answer = protocol_answer.dig(:answers).first %>
-
<%= entry.with_input do %>
-
then: 0
else: 0
<%= render LooposUi::PageSection::ItemTab::ProtocolResponses::ProtocolAnswerValue.new(kind: answer["type"]&.to_sym, value: answer["value"]) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= helpers.flex_cards_tab_section("Item", :show_trade_in_tab, instance_id: item.id) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module PageSection
-
1
module ItemTab
-
1
class ValidationWidget < LoopComponent
-
1
KEY = :validation_widget
-
-
1
option :item
-
end
-
end
-
end
-
end
-
<%
-
validation_id = LoopOsManager::Applications.get_manager_configs.dig("validation_widget", "validation_client_id")
-
validation_secret = LoopOsManager::Applications.get_manager_configs.dig("validation_widget", "validation_client_secret")
-
token = item.token
-
manager_url = ENV["LOOP_OS_MANAGER_URL"]
-
scopes = helpers.current_user.app_scopes_by_uid("validation", validation_id)
-
talk_js_application_id = ItemChatConfig.app_id
-
talk_js_api_key = ItemChatConfig.api_key
-
user = {id: helpers.current_user.id, full_name: helpers.current_user.full_name}
-
%>
-
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: "Validation Widget", description: I18n.t('admin.v2.items.validation_widget.description'), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
then: 0
<% if validation_id && validation_secret && token && manager_url && scopes %>
-
<div>
-
<%= react_component("WidgetValidation", {
-
itemToken: token,
-
managerURL: manager_url,
-
validationClientId: validation_id,
-
validationClientSecret: validation_secret ,
-
language: I18n.locale.to_s,
-
app: 'core',
-
user_scopes: scopes,
-
pricing_url: "",
-
updatedSelects: "",
-
user: user,
-
talkJSApplicationId: talk_js_application_id,
-
talkJSApiKey: talk_js_api_key,
-
env: "",
-
} ) %>
-
</div>
-
else: 0
<% else %>
-
<%= render LooposUi::TabsSection.new(description: t('admin.v2.items.tabs.page_not_available')) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiAnswerTab
-
1
class << self
-
1
def tab(key)
-
constants.filter_map do |subclass|
-
component = const_get(subclass)
-
then: 0
else: 0
component.const_get(:KEY) == key.to_sym ? component : nil
-
rescue NameError
-
nil
-
end.first
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiAnswerTab
-
1
class History < LoopComponent
-
1
KEY = :history
-
-
1
option :rfi
-
1
option :rfi_answer
-
1
option :item
-
end
-
end
-
end
-
end
-
<% url = helpers.loopos_ui.rfi_history_rfi_path(
-
rfi_id: rfi.id,
-
rfi_answer_id: rfi_answer.id,
-
item_token: item.token,
-
context: rfi_answer.source.source_context
-
) %>
-
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= tag.turbo_frame(id: "rfi_history_answers_content_#{rfi_answer.id}", src: url) do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiAnswerTab
-
1
class Info < LoopComponent
-
1
KEY = :info
-
-
1
option :rfi
-
1
option :rfi_answer
-
1
option :item
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.rfi_answer.tabs.info.title"), description: I18n.t("admin.rfi_answer.tabs.info.description"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.rfi_answer.tabs.info.details.title"), size: "small", underline: true) do %>
-
then: 0
else: 0
<% if rfi_answer.rfi_details.present? %>
-
<% rfi_answer.rfi_details.each do |entry| %>
-
then: 0
<% if entry[:type] == :protocol_answers && entry[:entries].present? %>
-
<% entry[:entries].each do |protocol_entry| %>
-
<%= render LooposUi::FormEntry.new(
-
label: protocol_entry[:label],
-
required: false,
-
orientation: "horizontal",
-
label_width: 140
-
) do |form_entry| %>
-
<%= form_entry.with_input do %>
-
<% raw_value = protocol_entry[:value] %>
-
then: 0
<% if raw_value.is_a?(Array) && raw_value.first.is_a?(Hash) && (raw_value.first[:url] || raw_value.first["url"] || raw_value.first[:name] || raw_value.first["name"]).present? %>
-
<% attachments = raw_value %>
-
-
<% image_urls = attachments.filter_map do |attachment|
-
url = attachment[:url] || attachment["url"]
-
filename = attachment[:name] || attachment["name"] || (File.basename(URI.parse(url).path.presence || url) rescue url)
-
then: 0
else: 0
next if url.blank? && filename.blank?
-
then: 0
else: 0
(filename =~ /\.(png|jpe?g|gif|webp)$/i || url =~ /\.(png|jpe?g|gif|webp)$/i) ? url : nil
-
end %>
-
-
<% file_urls = attachments.filter_map do |attachment|
-
url = attachment[:url] || attachment["url"]
-
filename = attachment[:name] || attachment["name"] || (File.basename(URI.parse(url).path.presence || url) rescue url)
-
then: 0
else: 0
next if url.blank? && filename.blank?
-
then: 0
else: 0
(filename =~ /\.(png|jpe?g|gif|webp)$/i || url =~ /\.(png|jpe?g|gif|webp)$/i) ? nil : url
-
end %>
-
-
then: 0
else: 0
<% if image_urls.any? %>
-
<%= react_component("ImagesGallery", { images: image_urls, size: "small" }) %>
-
<% end %>
-
-
then: 0
else: 0
<% if file_urls.any? %>
-
<%= react_component("FilesGallery", { files: file_urls, size: "small", railsIcon: true }) %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(
-
name: "protocol_answer_#{protocol_entry[:element_id]}",
-
value: raw_value.to_s,
-
readonly: true
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::FormEntry.new(label: entry[:label], required: false, orientation: "horizontal", label_width: 140) do |form_entry| %>
-
<%= form_entry.with_input do %>
-
<% case entry[:type] %>
-
when: 0
<% when :categories %>
-
then: 0
<% if entry[:value].present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
<% entry[:value].each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: entry[:key], value: "-", readonly: true) %>
-
<% end %>
-
when: 0
<% when :brand %>
-
<%= render LooposUi::Entities::Brand.new(brand: entry[:value]) %>
-
when: 0
<% when :product %>
-
<%= render LooposUi::Entities::Product.new(product: entry[:value]) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: entry[:key], value: entry[:value].to_s, readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiTab
-
1
class << self
-
1
def tab(key)
-
constants.filter_map do |subclass|
-
component = const_get(subclass)
-
then: 0
else: 0
component.const_get(:KEY) == key.to_sym ? component : nil
-
rescue NameError
-
nil
-
end.first
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiTab
-
1
class AdvancedDetails < LoopComponent
-
1
KEY = :rfi_advanced_details
-
-
1
option :rfi
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: I18n.t("admin.items.rfi.tabs.extra_data"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::ExtraDataViewer.new(
-
title: I18n.t("admin.items.rfi.tabs.extra_data"),
-
data: rfi.extra_data.to_json,
-
readonly: true,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiTab
-
1
class Info < LoopComponent
-
1
KEY = :rfi_info
-
-
1
option :rfi
-
1
option :item
-
1
option :rfi_final_choice, optional: true
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
then: 0
else: 0
<% if rfi_final_choice.present? %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render "loopos_ui/rfi/final_choice_card", rfi_name: rfi.name, rfi_final_choice: rfi_final_choice, hide_rfi_identification: true %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FlexLayout.new(size: 6, grow: true) do |layout| %>
-
<% layout.with_section(size: 4) do %>
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.rfi.tabs.info.rfi_details"), size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.rfi.tabs.info.requested_by"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if rfi.requested_by_name.present? %>
-
<%= render LooposUi::EntityToken.new(text: rfi.requested_by_name, icon: rfi.requested_by_logo) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "requested_by", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.rfi.tabs.info.responders"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if rfi.responders.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% rfi.responders.each do |responder| %>
-
<% (responder[:store_names] || responder["store_names"]).each do |store_name| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::EntityToken.new(text: store_name, icon: responder[:kind] || responder["kind"]) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "responders", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.rfi.tabs.info.final_choice_id"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
else: 0
<%= render LooposUi::Inputs::Text.new(name: "final_choice_id", value: rfi.final_choice.present? ? rfi.final_choice.id_label : "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.rfi.tabs.info.expiration_date"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: rfi.expiration_date) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% layout.with_section(size: 2) do %>
-
<div class="ml-4">
-
<%= render LooposUi::TabsSection.new(title: I18n.t("admin.items.item_info.item_details.title"), size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.item_id"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Entities::Item.new(item: item, url: item.show_url) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.category"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
<% item.categories.each do |category| %>
-
<%
-
then: 0
else: 0
category_name = category.respond_to?(:name) ? category.name : category
-
category_url = item.category_url(category)
-
category = Struct.new(:name).new(category_name)
-
%>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.brand"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<% if item.brand.present? %>
-
then: 0
<%
-
then: 0
else: 0
brand_name = item.brand.respond_to?(:name) ? item.brand.name : item.brand
-
brand_url = item.brand_url(item.brand)
-
brand = Struct.new(:name).new(brand_name)
-
%>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "brand", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.product"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<% if item.product.present? %>
-
then: 0
<%
-
then: 0
else: 0
product_name = item.product.respond_to?(:name) ? item.product.name : item.product
-
product_url = item.product_url(item.product)
-
product = Struct.new(:name).new(product_name)
-
%>
-
<%= render LooposUi::Entities::Product.new(product: product, url: product_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.option_values"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if item.option_values.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 4) do |token_list| %>
-
<% item.option_values.each do |option| %>
-
<% token_list.with_token_manual do %>
-
<%
-
then: 0
else: 0
option_name = option.respond_to?(:name) ? option.name : option[:value]
-
option = Struct.new(:name).new(option_name)
-
%>
-
<%= render LooposUi::EntityToken.new(text: option.name) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "option_values_empty", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.variant"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<% if item.variant_name.present? %>
-
then: 0
<%
-
variant_name = item.variant_name
-
variant_url = item.variant_url
-
%>
-
<%= render LooposUi::Token.new(text: variant_name, url: variant_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "variant", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.hubs_store"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Entities::AppInstance.new(app_instance: OpenStruct.new(name: item.hub_origin_name, kind: "hubs")) %>
-
<% end %>
-
<% end if item.hub_origin_name.present? %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.created_at"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: item.created_at) %>
-
<% end if item.created_at.present? %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: I18n.t("admin.items.item_info.item_details.updated_at"), required: false, orientation: "horizontal", label_width: 140) do |entry| %>
-
then: 0
else: 0
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: item.updated_at) %>
-
<% end if item.updated_at.present? %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module PageSection
-
1
module RfiTab
-
1
class RfiAnswers < LoopComponent
-
1
KEY = :rfi_answers
-
-
1
option :rfi
-
1
option :item
-
end
-
end
-
end
-
end
-
<%
-
url = helpers.loopos_ui.rfi_answers_rfi_path(
-
rfi_id: rfi.id,
-
item_token: item.token,
-
context: rfi.source.source_context
-
)
-
%>
-
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= tag.turbo_frame(id: "rfi_answers_index_table_#{rfi.id}", src: url) do %>
-
<%= render LooposUi::Loadings::Skeleton.new(full: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.turbo_frame id: "item_show_tabs" do %>
-
<% tab_url_base = { token: item.token, context: item.source.source_context } %>
-
<%= render LooposUi::TabsLayout.new(active_tab_key: active_tab_key) do |layout| %>
-
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.item_info"),
-
key: LooposUi::PageSection::ItemTab::Info::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Info::KEY),
-
lazy_load: true,
-
) %>
-
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.notes"),
-
key: LooposUi::PageSection::ItemTab::Notes::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Notes::KEY),
-
lazy_load: true,
-
) %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.trade_in"),
-
key: LooposUi::PageSection::ItemTab::TradeIn::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::TradeIn::KEY),
-
lazy_load: true,
-
) if item.forward_item.present? %>
-
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.protocol_answers"),
-
key: LooposUi::PageSection::ItemTab::ProtocolResponses::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::ProtocolResponses::KEY),
-
lazy_load: true,
-
) %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.client_info"),
-
key: LooposUi::PageSection::ItemTab::CustomerInfo::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::CustomerInfo::KEY),
-
lazy_load: true,
-
) if show_client_info? %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.flow"),
-
key: LooposUi::PageSection::ItemTab::Flow::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Flow::KEY),
-
lazy_load: true,
-
) if LooposUi.config.app_type?(:core)%>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.rfi"),
-
key: LooposUi::PageSection::ItemTab::Rfi::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Rfi::KEY),
-
lazy_load: true,
-
) if enable_rfi_feature? && can_view_rfis? && item.has_rfis? %>
-
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.services"),
-
key: LooposUi::PageSection::ItemTab::Services::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Services::KEY),
-
lazy_load: true,
-
) %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.financial_data"),
-
key: LooposUi::PageSection::ItemTab::FinancialData::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::FinancialData::KEY),
-
lazy_load: true,
-
) if show_financial_data? %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.logs"),
-
key: LooposUi::PageSection::ItemTab::Logs::KEY,
-
url: LooposUi::Engine.routes.url_helpers.logs_item_path(token: item.token, context: item.source.source_context),
-
lazy_load: true,
-
) if show_logs? %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.chat"),
-
key: LooposUi::PageSection::ItemTab::Chat::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::Chat::KEY),
-
lazy_load: true,
-
) if LooposUi.config.app_type?(:core)%>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.advanced_details"),
-
key: LooposUi::PageSection::ItemTab::AdvancedDetails::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::AdvancedDetails::KEY),
-
lazy_load: true,
-
load_only_on_click: true,
-
) if can_view_advanced_details? %>
-
then: 0
else: 0
<% layout.with_tab(
-
title: I18n.t("admin.items.tabs.validation_widget"),
-
key: LooposUi::PageSection::ItemTab::ValidationWidget::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::ValidationWidget::KEY),
-
lazy_load: true,
-
) if LooposUi.config.app_type?(:core) %>
-
then: 0
else: 0
<% layout.with_impact(
-
title: I18n.t("admin.items.tabs.impact_widget"),
-
icon: impact_icon,
-
key: LooposUi::PageSection::ItemTab::ImpactWidget::KEY,
-
url: LooposUi::Engine.routes.url_helpers.tab_item_path(**tab_url_base, tab_key: LooposUi::PageSection::ItemTab::ImpactWidget::KEY),
-
lazy_load: true,
-
) if LooposUi.config.app_type?(:core) %>
-
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Pages
-
1
class ItemShow < LoopComponent
-
1
TSource = Types::Instance(LooposUi::PageSources::Core) | Types::Instance(LooposUi::PageSources::CoreApi)
-
-
1
option :source, TSource
-
-
1
option :policy,
-
Types.Instance(LooposUi::AllAllowedPolicy) |
-
Types.Instance(LooposUi::NoneAllowedPolicy) |
-
Types.Instance(LooposUi::ReviewProcessPolicy),
-
optional: true,
-
default: -> {
-
then: 0
if ::LooposUi::Current.user
-
::Pundit.policy(::LooposUi::Current.user, [:loopos_ui, :review_process])
-
else: 0
else
-
::LooposUi::NoneAllowedPolicy.new
-
end
-
}
-
-
1
option :token, Types::String
-
-
1
option :logs_presenter, optional: true
-
-
1
option :active_tab_key, optional: true
-
-
1
def initialize(...)
-
super
-
then: 0
else: 0
if policy.is_a?(LooposUi::ReviewProcessPolicy)
-
@policy = policy.check!
-
end
-
end
-
-
1
def before_render
-
end
-
-
1
private
-
-
1
def impact_icon
-
"fa-regular fa-seedling"
-
end
-
-
1
def item
-
@item ||= source.item(token)
-
end
-
-
1
def show_logs?
-
item_from_model? || policy.can_view_item_logs?
-
end
-
-
1
def show_financial_data?
-
item_from_model? || policy.can_view_payments?
-
end
-
-
1
def show_client_info?
-
item_from_model? || policy.can_view_client_info?
-
end
-
-
1
def can_edit_client_info?
-
policy.can_edit_client_info?
-
end
-
-
1
def can_edit_client_iban?
-
item_from_model? || policy.can_edit_client_iban?
-
end
-
-
1
def item_from_model?
-
source.is_a?(LooposUi::PageSources::Core)
-
end
-
-
1
def can_manage_identifiers?
-
item_from_model? || policy.can_manage_identifiers?
-
end
-
-
1
def can_view_flows?
-
policy.can_view_flows?
-
end
-
-
1
def can_view_advanced_details?
-
then: 0
else: 0
LooposUi.config.app_type?(:core) && helpers.current_user&.can_view_extra_data?
-
end
-
-
1
def enable_rfi_feature?
-
enable_rfi_feature = begin
-
LoopOsManager::Applications.get_manager_configs.dig("enable_rfi_feature")
-
rescue
-
nil
-
end
-
then: 0
else: 0
enable_rfi_feature.present? ? enable_rfi_feature : false
-
end
-
-
1
def can_view_rfis?
-
policy.can_view_rfis?
-
end
-
-
1
def state_label_args
-
LooposUi::Item::StateLabel::ItemStruct.new(**item.state_badge_args)
-
end
-
end
-
end
-
end
-
<%= render LooposUi::IndexLayout.new do |layout| %>
-
<% layout.with_header(title: I18n.t("loopos_ui.reports.title", default: "Reports")) %>
-
-
then: 0
else: 0
<% if helpers.current_user.present? %>
-
<%= helpers.turbo_stream_from LooposUi::ReportsStream.channel_name(helpers.current_user.id) %>
-
<div id="loopos_ui_reports_stream_ping" hidden></div>
-
<% end %>
-
<%= tag.turbo_frame id: "loopos_ui_reports_table" do %>
-
<%= render LooposUi::Pages::ReportsIndexTable.new(
-
reports: reports,
-
pagy: pagy,
-
extra_data: extra_data,
-
core_instance_id: core_instance_id,
-
) %>
-
<% end %>
-
<% end %>
-
-
<%= content_for :action_bar do %>
-
<%= render LooposUi::ActionBar.new do |action_bar| %>
-
<% action_bar.with_breadcrumbs_list do |breadcrumbs| %>
-
<% breadcrumbs.with_breadcrumb(href: helpers.loopos_ui.reports_path) { t("loopos_ui.reports.title", default: "Reports") } %>
-
<% end %>
-
<% curr_export_options = export_options %>
-
<% action_bar.with_action_buttons do |action_buttons| %>
-
<% action_buttons.with_button_group do |bg| %>
-
then: 0
else: 0
<% bg.with_button_manual do %>
-
<%= render LooposUi::ActionMenu.new(options: curr_export_options) do |menu| %>
-
<% menu.with_trigger do %>
-
<% render(LooposUi::Button.new(
-
type: :tertiary,
-
kind: :neutral,
-
text: I18n.t("loopos_ui.reports.export", default: "Generate a new report"),
-
trailing_icon: "fa-regular fa-chevron-down",
-
icon: "file-arrow-down",
-
size: :tiny,
-
disabled: extra_data[:menu_disabled] || false,
-
tooltip_text: extra_data[:menu_tooltip].presence,
-
)) %>
-
<% end %>
-
<% end %>
-
<% end if curr_export_options.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Pages
-
1
class ReportsIndex < LoopComponent
-
1
DEFAULT_PAGE_SIZE = 15
-
1
option :reports, Types::Array, default: -> { [] }
-
1
then: 0
else: 0
option :pagy, Types.Instance(PageSources::Models::PagyWrapper), default: -> { PageSources::Models::PagyWrapper.new(page: 1, items: DEFAULT_PAGE_SIZE, count: reports&.count || 0) }
-
1
option :extra_data, Types::Hash, default: -> { {} }
-
1
option :core_instance_id, Types::Coercible::String, default: -> { "" }
-
-
1
def export_options
-
then: 0
else: 0
else: 0
then: 0
else: 0
case LooposUi.config.app_type&.to_sym
-
when: 0
when :hubs then
-
[
-
{
-
text: I18n.t("loopos_ui.reports.export_items", default: I18n.t("admin.v2.items.table.export_items")),
-
url: helpers.loopos_ui.reports_path(report_type: "hubs_items", filename: "items", core_instance_id: core_instance_id),
-
},
-
]
-
end&.each do |option|
-
option[:attributes] ||= {}
-
option[:attributes][:data] ||= {}
-
option[:attributes][:data][:turbo_method] = :post
-
# option[:attributes][:data][:turbo] = false
-
end || []
-
end
-
end
-
-
1
class ReportsIndexTable < LoopComponent
-
1
option :reports, Types::Array, default: -> { [] }
-
1
then: 0
else: 0
option :pagy, Types.Instance(PageSources::Models::PagyWrapper), default: -> { PageSources::Models::PagyWrapper.new(page: 1, items: DEFAULT_PAGE_SIZE, count: reports&.count || 0) }
-
1
option :extra_data, Types::Hash, default: -> { {} }
-
1
option :core_instance_id, Types::Coercible::String, default: -> { "" }
-
-
1
def table_columns
-
[
-
{ title: I18n.t("loopos_ui.reports.id", default: "ID"), dataIndex: "id", key: "id", hidable: false, sortable: true, sort_key: "id" },
-
{
-
title: I18n.t("loopos_ui.reports.report_type", default: "Type"),
-
key: "report_type",
-
dataIndex: "report_type",
-
sortable: true,
-
filters: LooposUi::PageSources::Models::Report::AVAILABLE_REPORT_TYPES.map { |report_type| { text: I18n.t("loopos_ui.reports.report_types.#{report_type}", default: report_type), value: report_type } },
-
},
-
{
-
title: I18n.t("loopos_ui.reports.status", default: "Status"),
-
key: "status",
-
dataIndex: "status",
-
type: :html,
-
sortable: true,
-
sort_key: "status",
-
filters: LooposUi::PageSources::Models::Report::REPORT_STATUSES.map { |status| { text: I18n.t("loopos_ui.reports.statuses.#{status}", default: status), value: status } },
-
},
-
{ title: I18n.t("loopos_ui.reports.created_at", default: "Created"), dataIndex: "created_at", key: "created_at", sortable: true, sort_key: "created_at", default_sort: :desc },
-
{ title: I18n.t("loopos_ui.reports.download", default: "Download"), dataIndex: "action", key: "action", type: :html },
-
]
-
end
-
-
1
def format_created_at(report)
-
then: 0
else: 0
return if report.created_at.blank?
-
-
Time.zone.parse(report.created_at.to_s).strftime("%Y-%m-%d %H:%M")
-
end
-
-
1
def show_download_button?(report)
-
report.status.to_s == "completed" && report.download_url.present?
-
end
-
-
1
def show_error_message?(report)
-
report.status.to_s == "failed" && report.error_message.present?
-
end
-
-
1
def reports_table_fetch_url
-
helpers.loopos_ui.reports_url(core_instance_id: core_instance_id)
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(
-
id: "reports_table",
-
columns: table_columns,
-
data: [],
-
pagy: pagy.presence,
-
show_pagination: true,
-
searchable: true,
-
show_result_count: true,
-
fetch_url: reports_table_fetch_url,
-
) do |table| %>
-
<% reports.each do |report| %>
-
<% table.with_row(key: report.id) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
<%= report.id %>
-
<% end %>
-
<% row.with_cell(property: :report_type) do %>
-
<%= I18n.t("loopos_ui.reports.report_types.#{report.report_type}", default: report.report_type.to_s) %>
-
<% end %>
-
<% row.with_cell(property: :status) do %>
-
<%= I18n.t("loopos_ui.reports.statuses.#{report.status}", default: report.status.to_s) %>
-
<% end %>
-
<% row.with_cell(property: :created_at) do %>
-
<%= format_created_at(report) %>
-
<% end %>
-
<% row.with_cell(property: :action) do %>
-
then: 0
<% if show_download_button?(report) %>
-
<%= render LooposUi::Button.new(
-
type: :secondary,
-
kind: :neutral,
-
text: I18n.t("loopos_ui.reports.download", default: "Download"),
-
icon: "file-arrow-down",
-
size: :tiny,
-
href: report.download_url,
-
load_on_click: false,
-
tag_options: {
-
target: "_blank",
-
rel: "noopener",
-
},
-
else: 0
) %>
-
then: 0
<% elsif show_error_message?(report) %>
-
<%= report.error_message %>
-
else: 0
<% else %>
-
<%= I18n.t("loopos_ui.reports.pending", default: "Pending...") %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= helpers.turbo_frame_tag "rfi_show_tabs" do %>
-
<%= render LooposUi::TabsLayout.new(active_tab_key: active_tab_key) do |layout| %>
-
<% layout.with_tab(title: I18n.t("admin.items.rfi.tabs.rfi_info"), key: LooposUi::PageSection::RfiTab::Info::KEY) do %>
-
<%= render LooposUi::PageSection::RfiTab::Info.new(rfi: rfi, item: item, rfi_final_choice: rfi.final_choice) %>
-
<% end %>
-
<% layout.with_tab(title: I18n.t("admin.items.rfi.tabs.rfi_answers"), key: LooposUi::PageSection::RfiTab::RfiAnswers::KEY) do %>
-
<%= render LooposUi::PageSection::RfiTab::RfiAnswers.new(rfi: rfi, item: item) %>
-
<% end %>
-
then: 0
else: 0
<% layout.with_tab(title: I18n.t("admin.items.rfi.tabs.advanced_details"), key: LooposUi::PageSection::RfiTab::AdvancedDetails::KEY) do %>
-
<%= render LooposUi::PageSection::RfiTab::AdvancedDetails.new(rfi: rfi) %>
-
<% end if source_type == :core && helpers.current_user.can_view_extra_data? %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Pages
-
1
module Rfi
-
1
class RfiShow < LoopComponent
-
1
TSource = Types::Instance(LooposUi::PageSources::Core) | Types::Instance(LooposUi::PageSources::CoreApi)
-
-
1
option :source, TSource, optional: true
-
-
# option :policy, optional: true, default: proc { NoPermissionPolicy.new }
-
-
1
option :rfi_id, Types::Integer
-
1
option :item_token, Types::String
-
1
option :active_tab_key, optional: true
-
-
1
def before_render
-
end
-
-
1
def source_type
-
else: 0
then: 0
return unless source
-
-
else: 0
case source
-
when: 0
when LooposUi::PageSources::CoreApi
-
:core_api
-
when: 0
when LooposUi::PageSources::Core
-
:core
-
end
-
end
-
-
1
private
-
-
1
def rfi
-
@rfi ||= LooposUi::PageSources::Models::Rfi.find_by(rfi_id: rfi_id, source: source)
-
end
-
-
1
def item
-
@item ||= LooposUi::PageSources::Models::Item.find_by(token: item_token, source: source)
-
end
-
end
-
end
-
end
-
end
-
<%= tag.turbo_frame id: "rfi_show_tabs" do %>
-
<%= render LooposUi::TabsLayout.new(active_tab_key: active_tab_key) do |layout| %>
-
<% layout.with_tab(title: I18n.t("admin.rfi_answer.tabs.info.title"), key: LooposUi::PageSection::RfiAnswerTab::Info::KEY) do %>
-
<%= render LooposUi::PageSection::RfiAnswerTab::Info.new(rfi: rfi, item: item, rfi_answer: rfi_answer) %>
-
<% end %>
-
<% layout.with_tab(title: I18n.t("admin.rfi_answer.tabs.history.title"), key: LooposUi::PageSection::RfiAnswerTab::History::KEY) do %>
-
<%= render LooposUi::PageSection::RfiAnswerTab::History.new(rfi: rfi, item: item, rfi_answer: rfi_answer) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Pages
-
1
module RfiAnswer
-
1
class Show < LoopComponent
-
1
TSource = Types::Instance(LooposUi::PageSources::Core) | Types::Instance(LooposUi::PageSources::CoreApi)
-
-
1
option :source, TSource, optional: true
-
-
# option :policy, optional: true, default: proc { NoPermissionPolicy.new }
-
-
1
option :rfi_answer_id, Types::Integer
-
1
option :rfi_id, Types::Integer
-
1
option :item_token, Types::String
-
1
option :active_tab_key, optional: true
-
-
1
def before_render
-
end
-
-
1
def source_type
-
else: 0
then: 0
return unless source
-
-
else: 0
case source
-
when: 0
when LooposUi::PageSources::CoreApi
-
:core_api
-
when: 0
when LooposUi::PageSources::Core
-
:core
-
end
-
end
-
-
1
private
-
-
1
def rfi
-
@rfi ||= LooposUi::PageSources::Models::Rfi.find_by(rfi_id: rfi_id, source: source)
-
end
-
-
1
def rfi_answer
-
@rfi_answer ||= LooposUi::PageSources::Models::RfiAnswers.find_by(rfi_answer_id: rfi_answer_id, source: source)
-
end
-
-
1
def item
-
@item ||= LooposUi::PageSources::Models::Item.find_by(token: item_token, source: source)
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Pagination < LoopComponent
-
1
option :pagy, Types::Instance(Pagy), optional: true
-
-
1
option :page, Types::Coercible::Integer, default: -> { 1 }
-
1
option :items, Types::Coercible::Integer, optional: true
-
1
option :count, Types::Coercible::Integer, optional: true
-
-
1
option :path, Types::String, optional: true
-
1
option :link_attrs, Types::Hash, default: -> { {} }
-
-
1
option :mode, Types::Symbol.enum(:default, :event), default: -> { :default } # Not implemented yet
-
-
1
option :data, Types::Hash, default: -> { {} }
-
-
1
def path
-
@path ||= request.fullpath
-
end
-
-
1
private
-
-
1
def initialize(**options)
-
super(**options)
-
-
then: 0
if @pagy.nil? && [:count, :page, :items].all? { |key| options.key?(key) }
-
else: 0
@pagy = Pagy.new(options)
-
then: 0
else: 0
elsif @pagy.nil?
-
raise ArgumentError, "Either pagy or count, page, and items must be provided"
-
end
-
end
-
-
1
def page
-
@pagy.page
-
end
-
-
1
def prev_path
-
page_path(@pagy.prev)
-
end
-
-
1
def next_path
-
page_path(@pagy.next)
-
end
-
-
1
def page_path(number)
-
then: 0
else: 0
return if number.blank?
-
-
uri = URI.parse(path)
-
path_params = Rack::Utils.parse_nested_query(uri.query)
-
path_params["page"] = number
-
-
# Re-encode the query string
-
uri.query = Rack::Utils.build_query(path_params)
-
uri.to_s
-
end
-
-
1
def before_render
-
@path ||= request.fullpath
-
end
-
-
1
def data
-
# Not used yet, but will be used for event mode
-
deep_merge_args(
-
{
-
controller: "lui--pagination",
-
},
-
@data,
-
)
-
end
-
-
1
def prev_button
-
Button.new(
-
icon: :chevron_left,
-
href: prev_path,
-
disabled: prev_path.nil?,
-
**button_options,
-
)
-
end
-
-
1
def next_button
-
Button.new(
-
icon: :chevron_right,
-
href: next_path,
-
disabled: next_path.nil?,
-
**button_options,
-
)
-
end
-
-
1
def button_options
-
{
-
type: :secondary,
-
kind: :neutral,
-
size: :small,
-
tag_options: @link_attrs,
-
}
-
end
-
-
1
def chunks
-
@pagy.series(size: [1, 2, 2, 1])
-
end
-
-
1
def pages
-
@pages ||= @pagy.pages.times.map { |p| p + 1 }
-
end
-
-
1
def item(number)
-
Item.new(number, pagination: self, active: number.to_s == page.to_s)
-
end
-
-
1
class Item < LoopComponent
-
1
param :number, Types::Coercible::Integer
-
1
option :pagination, Types::Instance(Pagination)
-
1
option :active, Types::Bool, default: -> { false }
-
-
1
mod :active
-
-
1
def call
-
content_tag(:a, href: path, class: classes, **pagination.link_attrs) do
-
number.to_s
-
end
-
end
-
-
1
def path
-
then: 0
else: 0
return if active
-
-
uri = URI.parse(pagination.path)
-
params = Rack::Utils.parse_nested_query(uri.query)
-
-
# Merge or change parameters
-
params["page"] = number
-
-
# Re-encode the query string
-
uri.query = Rack::Utils.build_query(params)
-
uri.to_s
-
end
-
end
-
end
-
end
-
<%= tag.span(class: "lui-pagination", data: data) do %>
-
<%= render prev_button %>
-
<% chunks.each do |n| %>
-
then: 0
<% if n == :gap %>
-
<%= tag.span("...", class: "lui-pagination__divider") %>
-
else: 0
<% else %>
-
<%= render item(n) %>
-
<% end %>
-
<% end %>
-
<%= render next_button %>
-
<% end %>
-
1
module LooposUi
-
1
class Popover < LoopComponent
-
1
renders_one :toggle, types: {
-
button: {
-
renders: ->(*args, **kwargs) {
-
LooposUi::Button.new(*args, tag: :span, **kwargs.deep_merge(button_attributes))
-
},
-
as: :toggle_button,
-
},
-
custom: {
-
renders: ->(&block) { capture(&block) },
-
as: :custom_toggle,
-
},
-
}
-
-
1
renders_one :target # TODO: Deprecate target, prefer content slot
-
-
1
option :id, default: -> { "popover-#{Random.hex(10)}" }
-
1
option :position, default: -> { :bottom_left }, type: Types::Symbol.enum(
-
:top_left,
-
:top_center,
-
:top_right,
-
:bottom_left,
-
:bottom_center,
-
:bottom_right,
-
)
-
-
1
option :mode, default: -> { :auto }, type: Types::Symbol.enum(:auto, :manual)
-
-
1
option :anchor_selector, optional: true # the selector of the element to anchor to (if not provided, the popover will be anchored to the toggle)
-
-
1
option :anchor, default: -> { :top_left }, type: Types::Symbol.enum(
-
:top_left,
-
:top_center,
-
:top_right,
-
:bottom_left,
-
:bottom_center,
-
:bottom_right,
-
)
-
-
1
option :on_close, optional: true, type: Types::Symbol.enum(:refresh)
-
1
option :rotate_toggle, default: -> { false }
-
1
option :open, Types::Bool, default: -> { false }
-
-
# TODO: make all components follow this pattern
-
# Inspired by https://primer.style/view-components/lookbook/pages/system_arguments/
-
# Maybe change the name, and define which arguments are allowed
-
1
option :system_arguments, default: -> { {} }
-
-
1
class << self
-
skipped
# :nocov:
-
skipped
def positions
-
skipped
dry_initializer.definitions[:position].type.values
-
skipped
end
-
skipped
-
skipped
def anchors
-
skipped
dry_initializer.definitions[:anchor].type.values
-
skipped
end
-
skipped
# :nocov:
-
end
-
-
1
def toggle_attributes
-
{
-
popovertarget: popover_target_id,
-
data: {
-
controller: "popover-toggle",
-
popover_target: "toggle",
-
popover_toggle_position_value: position.to_s,
-
popover_toggle_anchor_value: anchor.to_s,
-
popover_toggle_anchor_selector_value: anchor_selector,
-
popover_toggle_on_close_value: on_close.to_s,
-
popover_toggle_rotate_value: rotate_toggle,
-
},
-
}
-
end
-
-
1
def button_attributes
-
{
-
tag_options: toggle_attributes,
-
}
-
end
-
-
1
private
-
-
1
def args
-
deep_merge_args(system_arguments, {
-
data: {
-
controller: "popover",
-
popover_open_value: open.to_s,
-
popover_rotate_toggle_value: rotate_toggle,
-
},
-
class: "lui-popover",
-
})
-
end
-
-
1
def popover_target_id
-
"#{id}-target"
-
end
-
end
-
end
-
<%= tag.div(**args) do %>
-
<%= tag.button(**toggle_attributes, type: :button, class: "lui-popover-toggle-wrapper") do %>
-
<%= toggle %>
-
<% end %>
-
<%= tag.div(id: popover_target_id, popover: mode, data: { popover_target: "popover" }, class: "pointer-events-none w-full h-full bg-transparent overflow-hidden") do %>
-
<div class="bg-white lui-popover-inner absolute pointer-events-auto">
-
<%= content || target %>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class PopoverTemplate < LoopComponent
-
1
option :title, Types::String
-
1
renders_one :input
-
1
renders_one :entity_token
-
end
-
end
-
<%= render LooposUi::Popover.new do |popover| %>
-
<%= popover.with_custom_toggle do %>
-
<%= entity_token %>
-
<% end %>
-
<%= popover.with_target do %>
-
<div class="lui-popover_template" data-controller="popover-template">
-
<%= render LooposUi::FormEntry.new(label: title) do |form_entry| %>
-
<%= form_entry.with_input do %>
-
<%= input %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<%= tag.div data: data_attributes do %>
-
<%= header %>
-
<div data-async-select-component-target='dropdown' data-dropdown-toggle-target="<%= dropdown_id %>" class="loopui-async-select__tag">
-
-
-
<div class="protocol-add-btn" data-action="click->async-select-component#filterInput">
-
<div class="protocol-add-btn__inside self-stretch h-[67px] px-4 py-6 rounded-lg border-2 border-orange-200 flex-col justify-center items-center gap-4 flex">
-
<div class="w-[574px] h-[19px] text-center text-orange-700 copy-14-medium"><%= t(".add_element") %></div>
-
</div>
-
</div>
-
-
</div>
-
<div id="<%= dropdown_id %>" class="hidden loopui-async-select__dropdown loopui-async-select__dropdown--protocol">
-
<div class="p-3">
-
<label for="input-group-search" class="loopui-async-select__label"><%= t(".search") %></label>
-
<div class="relative">
-
<div class="loopui-async-select__search-icon">
-
<i class="fa-regular fa-magnifying-glass"></i>
-
</div>
-
<input
-
id="input-group-search"
-
type="text"
-
data-action='keydown->async-select-component#filterKeyDown input->async-select-component#filterInput'
-
class="loopui-async-select__search-input"
-
placeholder=<%= t(".search_placeholder") %>>
-
</div>
-
</div>
-
<div class="tags-filter-container loopui-async-select__filter-container loopui-async-select__filter-container--protocol" aria-labelledby="dropdownSearchButton">
-
<%= turbo_frame_tag filter_wrapper_id, data:{ "async-select-component-target": "dropdownTurbo"} do %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
-
-
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Protocol
-
1
module AddElement
-
1
class AddElementComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :header
-
1
renders_many :options, LooposUi::Protocol::AddElement::SelectOptionComponent
-
-
1
attr_accessor :object, :filter_wrapper_id, :param_key
-
-
1
def initialize(filter_path:,
-
assign_path:,
-
import_path:,
-
data_attributes: {},
-
options: [],
-
object: nil,
-
filter_wrapper_id: nil,
-
param_key: "name")
-
@filter_path = filter_path
-
@assign_path = assign_path
-
@import_path = import_path
-
@data_attributes = data_attributes
-
@object = object
-
@filter_wrapper_id = filter_wrapper_id
-
@param_key = param_key
-
end
-
-
1
def dropdown_id
-
# Maybe we can unique property for the select component, random for now
-
@dropdown_id ||= SecureRandom.hex(10)
-
end
-
-
1
def data_attributes
-
{
-
controller: "async-select-component",
-
"async-select-component-filter-path-value": @filter_path,
-
"async-select-component-assign-path-value": @assign_path,
-
"async-select-component-import-path-value": @import_path,
-
"async-select-component-param-key-value": @param_key,
-
}.merge!(@data_attributes)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Protocol
-
1
module AddElement
-
1
class FilterComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_accessor :resource, :filtered_resource, :filter_wrapper_prefix
-
-
1
def initialize(resource:, filtered_resource:, filter_wrapper_prefix:)
-
@resource = resource
-
@filtered_resource = filtered_resource
-
@filter_wrapper_prefix = filter_wrapper_prefix
-
end
-
end
-
end
-
end
-
end
-
<%= turbo_stream.update "#{@filter_wrapper_prefix}#{dom_id(@resource)}" do %>
-
<div class="loopui-async-select__options-grid">
-
then: 0
<% if @filtered_resource.present? %>
-
<% @filtered_resource.group_by(&:third).each do |group, bds| %>
-
<% element_title_dictionary = {
-
'Input' => t(".input"),
-
'Info' => t(".info"),
-
'Structure' => t(".structure"),
-
'Paste' => t(".paste")
-
} %>
-
<%
-
element_tooltip_copies = {
-
'Input' => t(".input_tooltip"),
-
'Info' => t(".info_tooltip"),
-
'Structure' => t(".structure_tooltip"),
-
'Paste' => t(".paste_tooltip")
-
}
-
%>
-
<div class="loopui-async-select__options-grid--<%= group %>">
-
<div class="loopui-async-select__options-title copy-12 font-bold">
-
<%= element_title_dictionary[group] %>
-
<% icon = capture do %>
-
<i class="loopui-async-select__options-title copy-12 font-bold fa-solid fa-circle-info"></i>
-
<% end %>
-
<%= react_component("Tooltip", { text: element_tooltip_copies[group], children: icon, placement: "top" }) %>
-
</div>
-
<% bds.each do |bd| %>
-
<%= render LooposUi::Protocol::AddElement::SelectOptionComponent.new(label: bd[0], icon: bd[3], type: bd[2], object: bd, submit_value: bd[1]) %>
-
<% end %>
-
</div>
-
<% end %>
-
else: 0
<% else %>
-
<p class="loopui-async-select__options-title copy-12 font-bold">No results found</p>
-
<% end %>
-
<% end %>
-
<%= turbo_frame_tag "filter-#{object_identifier}" do %>
-
<%= tag.div( class:"w-full", data: { action: "click->async-select-component#labelClick", payload: data_payload, value: submit_value }.merge(label_data)) do %>
-
then: 0
else: 0
<div <%= data_attributes.map { |key, value| "data-#{key}=#{value}" }.join(' ') if data_attributes.present? %>
-
class="filter-options-container async-select__options-wrapper loopui-async-select__options-wrapper--<%= @type %>">
-
<%= tag.div(input_id, class: "tags-label cursor-pointer tags-label-#{object_identifier} loopui-async-select__options-text loopui-async-select__options-text--protocol copy-12-medium") do %>
-
<div class="loopui-async-select__options-icon loopui-async-select__options-icon--<%= @type %>">
-
<i class="<%= @icon %>"></i>
-
</div>
-
<%= label %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Protocol
-
1
module AddElement
-
1
class SelectOptionComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :label_component
-
1
renders_one :edit_component
-
-
1
attr_accessor :label, :object, :data_payload, :data_attributes, :label_data, :submit_value
-
-
1
def initialize(label:, icon:, type:, object: nil, data_payload: {}, data_attributes: nil, label_data: {},
-
submit_value: nil)
-
@label = label
-
@icon = icon
-
@type = type
-
@object = object
-
@data_payload = data_payload
-
then: 0
else: 0
@data_attributes = data_attributes.respond_to?(:dig) ? data_attributes.compact_blank : {}
-
then: 0
else: 0
@label_data = label_data.respond_to?(:dig) ? label_data.compact_blank : {}
-
@submit_value = submit_value || label
-
end
-
-
1
def input_id
-
"input_label_#{object_identifier}"
-
end
-
-
1
def object_identifier
-
then: 0
else: 0
object.respond_to?(:dom_id) ? dom_id(object) : object.to_s
-
end
-
-
1
def theme
-
then: 0
else: 0
@object.respond_to?(:theme) ? @object.theme : nil
-
end
-
-
1
def color
-
then: 0
else: 0
return theme.text_color if theme.present? && object.persisted?
-
-
then: 0
else: 0
@color || (@object.respond_to?(:color) ? @object.color : "black")
-
end
-
-
1
def persisted?
-
then: 0
else: 0
@object.respond_to?(:persisted?) ? @object.persisted? : true
-
end
-
end
-
end
-
end
-
end
-
<div class="protocol-area__add-section">
-
<div class="protocol-area__title-section">
-
<div class="protocol-area__title-wrapper">
-
then: 0
else: 0
<% if @highlighted %>
-
<i class="copy-12 text-app-800-primary fa-kit fa-regular-bars-staggered-tag"></i>
-
<% end %>
-
then: 0
else: 0
<p class="text-app-800-primary copy-16 font-bold"><%= @highlighted ? t(".catalog_section"): t(".main_section") %></p>
-
<% icon = capture do %>
-
<i class="copy-12 text-app-800-primary fa-regular fa-circle-info"></i>
-
<% end %>
-
<%= react_component("Tooltip", {
-
then: 0
else: 0
text: @highlighted ? t(".tooltip_highlighted") : t(".tooltip_main_section"),
-
children: icon,
-
placement: "top" }) %>
-
</div>
-
then: 0
else: 0
<p class="protocol-area__text copy-14 text-gray-900"><span class="copy-14 text-app-800-primary"><%= t(".drag_here") %></span> <%= t(".to_move") %> <%= @highlighted ? t(".catalog_section"): t(".main_section") %> <%= t(".in_submission") %></p>
-
</div>
-
<div class="protocol-area__right" >
-
<%= form_tag import_admin_v2_protocols_path,
-
class: "flex",
-
data: {
-
controller: "autosubmit loading async-select-component modal clipboard",
-
autosubmit_target: "form",
-
turbo_frame: '_top',
-
"async-select-component-filter-path-value": filter_protocols_admin_v2_catalog_node_path(@catalog_node),
-
"async-select-component-assign-path-value": attach_protocol_admin_v2_catalog_node_path(@catalog_node),
-
"async-select-component-param-key-value": "protocol_id",
-
assign_method: "POST",
-
payload_data: {
-
catalog_node_id: @catalog_node.id,
-
highlight: @highlighted,
-
}
-
},
-
multipart: true do %>
-
<%= hidden_field_tag :catalog_node_id, @catalog_node.id %>
-
<%= hidden_field_tag :highlight, @highlighted %>
-
<%
-
dropdown_id = SecureRandom.hex(10)
-
choose_existing_modal_id = "choose_existing_protocol_#{dom_id(@catalog_node)}"
-
%>
-
-
<%
-
sub_buttons = [
-
(
-
{ text: t(".import_protocol"), app: "core", icon: "fa-regular fa-file-export", variant: "secondary", size: "large", iconPosition: "left", railsIcon: true, dataAttributes: { "loading-target": "toggle", action: "click->autosubmit#trigger" }}
-
),
-
(
-
{ text: t(".choose_existing"), app: "core", icon: "fa-regular fa-list", variant: "secondary", size: "large", iconPosition: "left", railsIcon: true, dataAttributes: { action: "click->modal#open click->async-select-component#filterInput", "modal-id": choose_existing_modal_id}}
-
),
-
(
-
{ text: t(".paste_protocol"), app: "core", icon: "fa-regular fa-paste", variant: "secondary", size: "large", iconPosition: "left", railsIcon: true, dataAttributes: { "loading-target": "toggle", action: "click->clipboard#paste", "clipboard-import-param": import_admin_v2_protocols_path, "clipboard-node-param": @catalog_node.id, "clipboard-highlight-param": @highlighted }}
-
),
-
].compact
-
%>
-
<%= render LooposUi::Modal.new(id: choose_existing_modal_id, title: t(".choose_existing")) do |modal| %>
-
<% modal.with_custom_content do %>
-
<div class="p-3 w-full">
-
<label for="input-group-search" class="loopui-async-select__label"><%= t(".search") %></label>
-
<div class="relative">
-
<div class="loopui-async-select__search-icon">
-
<i class="fa-regular fa-magnifying-glass"></i>
-
</div>
-
<input
-
id="input-group-search"
-
type="text"
-
data-action='keydown->async-select-component#filterKeyDown input->async-select-component#filterInput'
-
class="loopui-async-select__search-input"
-
placeholder= <%= t(".search_placeholder") %>>
-
</div>
-
</div>
-
<div class="h-[300px] tags-filter-container loopui-async-select__filter-container loopui-async-select__filter-container--protocol" aria-labelledby="dropdownSearchButton">
-
then: 0
else: 0
<%= turbo_frame_tag "#{@highlighted ? "catalog": "main"}_attach_#{dom_id(@catalog_node)}", data:{ "async-select-component-target": "dropdownTurbo"} do %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
-
<%= react_component("ButtonGroup", { text: "", app: 'core', variant: "secondary", size: "large", icon: "fa-regular fa-ellipsis", iconPosition: "left", buttonsPosition: "right", subButtons: sub_buttons}) %>
-
<%= file_field_tag 'import_file', accept: '.json', id: "form", class: 'cursor-pointer', style: 'display: none; position: absolute', data: { action: "change->autosubmit#submit change->loading#setToggleLoadingNoMargin", "autosubmit-target": "input" } %>
-
<% end %>
-
<%= form_with(
-
model: @model,
-
method: :post,
-
class: "flex",
-
data: { turbo_frame: @turbo_frame_id }) do |form| %>
-
<%= form.hidden_field :catalog_node_id, value: @catalog_node.id %>
-
<%= form.hidden_field :name, value: "#{@catalog_node.name} Protocol" %>
-
then: 0
else: 0
<% if @highlighted %>
-
<%= form.hidden_field :highlight, value: true %>
-
<% end %>
-
then: 0
else: 0
<% if @current_user.can_edit_protocols? %>
-
<%= form.button do %>
-
<%= react_component("Button", { text: t(".create_new"), app: "core", variant: "primary", size: "large", iconPosition: "left"}) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Protocol
-
1
module AttachNode
-
1
class FilterComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_accessor :resource, :filtered_resource, :filter_wrapper_prefix
-
-
1
def initialize(resource:, filtered_resource:, filter_wrapper_prefix:)
-
@resource = resource
-
@filtered_resource = filtered_resource
-
@filter_wrapper_prefix = filter_wrapper_prefix
-
end
-
end
-
end
-
end
-
end
-
<%= turbo_stream.update "#{@filter_wrapper_prefix}#{dom_id(@resource)}" do %>
-
then: 0
<% if @filtered_resource.present? %>
-
<% @filtered_resource.each do |bd| %>
-
<%= render LooposUi::Protocol::AttachNode::SelectOptionComponent.new(label: bd.name, object: bd, submit_value: bd.id) %>
-
<% end %>
-
else: 0
<% else %>
-
No results found
-
<% end %>
-
<% end %>
-
<%
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
option_style = "background-color: #{theme&.bg_color}; color: #{theme&.text_color}; border-color: #{theme&.border_color}" if theme.present?
-
then: 0
else: 0
then: 0
else: 0
text_color_style = "color: #{theme&.text_color};" if theme.present?
-
%>
-
-
<%= turbo_frame_tag "filter-#{object_identifier}" do %>
-
<div
-
then: 0
else: 0
<%= data_attributes.map { |key, value| "data-#{key}=#{value}" }.join(' ') if data_attributes.present? %>
-
class="filter-options-container flex items-center py-1 px-2 rounded-md group border border-white hover:bg-background hover:border hover:border-primary-dark-10 transition-colors"
-
style="<%= option_style %>"
-
>
-
<%= tag.div(class: "w-full", data: { action: "click->async-select-component#labelClick", payload: data_payload, value: submit_value }.merge(label_data)) do %>
-
then: 0
<% if label_component? %>
-
<span class="group-hover:text-primary-dark flex-1 text-xs font-medium text-gray-dark"><%= label_component %></span>
-
else: 0
<% else %>
-
<%= tag.label(input_id, class: "tags-label cursor-pointer tags-label-#{object_identifier} flex-1 text-xs font-medium text-gray-dark rounded-lg", style: text_color_style) do %>
-
<%= label %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
then: 0
<% if persisted? %>
-
then: 0
else: 0
<% if edit_component.present? %>
-
<%= edit_component %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(class: "w-full", data: { action: "click->async-select-component#labelClick", payload: data_payload, value: submit_value }.merge(label_data)) do %>
-
<span class='text-[10px] font-bold group-hover:underline group-hover:text-primary-dark text-gray-base cursor-pointer'>create new</span>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Protocol
-
1
module AttachNode
-
1
class SelectOptionComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :label_component
-
1
renders_one :edit_component
-
-
1
attr_accessor :label, :object, :data_payload, :data_attributes, :label_data, :submit_value
-
-
1
def initialize(label:, object: nil, data_payload: {}, data_attributes: nil, label_data: {}, submit_value: nil)
-
@label = label
-
@object = object
-
@data_payload = data_payload
-
then: 0
else: 0
@data_attributes = data_attributes.respond_to?(:dig) ? data_attributes.compact_blank : {}
-
then: 0
else: 0
@label_data = label_data.respond_to?(:dig) ? label_data.compact_blank : {}
-
@submit_value = submit_value || label
-
end
-
-
1
def input_id
-
"input_label_#{object_identifier}"
-
end
-
-
1
def object_identifier
-
then: 0
else: 0
object.respond_to?(:dom_id) ? dom_id(object) : object.to_s
-
end
-
-
1
def theme
-
then: 0
else: 0
@object.respond_to?(:theme) ? @object.theme : nil
-
end
-
-
1
def color
-
then: 0
else: 0
return theme.text_color if theme.present? && object.persisted?
-
-
then: 0
else: 0
@color || (@object.respond_to?(:color) ? @object.color : "black")
-
end
-
-
1
def persisted?
-
then: 0
else: 0
@object.respond_to?(:persisted?) ? @object.persisted? : true
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Protocol
-
1
class DrawerBar < LoopComponent
-
1
option :protocol
-
1
option :catalog_node
-
-
1
private
-
-
1
def presenter
-
@presenter ||= protocol.presenter
-
end
-
end
-
end
-
end
-
<%= render LooposUi::DrawerBar.new do |drawer| %>
-
<% drawer.with_header do %>
-
<span class="inline-flex p-4">
-
<%= tag.turbo_frame id: dom_id(protocol, 'tags') do %>
-
<%= render 'admin/v2/tags/tags', taggable: protocol %>
-
<% end %>
-
</span>
-
<% end %>
-
<% drawer.with_header do %>
-
<div class="px-4 pb-4 flex flex-col self-stretch gap-2">
-
<%= render LooposUi::TitleDescription.new(
-
title: t(".protocol_settings"),
-
description: ""
-
)%>
-
<div class="flex flex-col gap-2 self-stretch">
-
then: 0
else: 0
<% if presenter.loopos_ai_protocol_generator_path.present? %>
-
<div
-
data-controller="ai-protocol"
-
data-ai-protocol-protocol-id-value="<%= protocol.id %>"
-
data-ai-protocol-catalog-node-id-value="<%= catalog_node.id %>"
-
data-ai-protocol-generator-path-value="<%= presenter.loopos_ai_protocol_generator_path %>"
-
data-ai-protocol-waiting-path-value="<%= waiting_for_ai_admin_v2_protocol_path(protocol) %>"
-
>
-
<%= render LooposUi::FormEntryAi.new(
-
description: "Start the prompt with an action. Give all the needed details.",
-
placeholder: "Generate protocol with LoopOS AI",
-
) %>
-
</div>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(
-
label: "Description",
-
required: false,
-
orientation: "vertical",
-
) do |entry| %>
-
<%= entry.with_input do %>
-
<%= tag.turbo_frame id: "edit_protocol_description" do %>
-
<%= form_with model: protocol, url: admin_v2_protocol_path(protocol), method: :patch, data: { turbo_frame: "edit_protocol_description" }, html: { id: "edit_protocol_description_form" } do %>
-
<%= render LooposUi::Inputs::TextArea.new(
-
model: protocol,
-
attribute: :description,
-
placeholder: "Insert a protocol description",
-
rows: 4,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<%= render LooposUi::Protocol::Settings.new(
-
protocol: protocol,
-
presenter: presenter,
-
catalog_node: catalog_node,
-
) %>
-
<% end %>
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
-
<div class="flex flex-col w-full gap-2">
-
<div class="flex flex-row gap-2xs">
-
<p class="text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
</div>
-
<%= turbo_frame_tag element, attribute, "tag_list" do %>
-
else: 0
<% if properties[:value] != "-" %>
-
then: 0
<%
-
list = element.array_property(attribute).map do |option|
-
{
-
title: "#{option.label}",
-
variant: 'primary',
-
kind: "core",
-
tSize: 'extraLarge',
-
icon: "fa-regular fa-seal",
-
line: "solid",
-
deleteDataAttributes: {
-
'action':"click->modal#open click->tag-delete#setmodal",
-
"link": unassign_array_options_admin_v2_protocol_element_path(element, attribute: attribute, value: option.value),
-
"target": "patch",
-
},
-
configurable: true,
-
deleteIcon: "fa-regular fa-times"
-
}
-
end
-
%>
-
<div data-controller="tag-delete modal">
-
<%= render "shared/components/modal", type: properties[:label].titleize do %>
-
<button data-tag-delete-target="modalbtn"
-
class="core-button core-button-primary core-button-large">
-
Confirm
-
</button>
-
<% end %>
-
<%= react_component("TagList", {
-
catList: list,
-
maxDisplayed: '2',
-
tagList: list,
-
tSize: 'extraLarge',
-
tooltip: attribute.to_s.singularize.titleize
-
}) %>
-
</div>
-
<% end %>
-
<% end %>
-
<%= render AsyncSelectComponent.new(
-
filter_path: array_options_admin_v2_protocol_element_path(element),
-
assign_path: assign_array_options_admin_v2_protocol_element_path(element),
-
filter_wrapper_id: "array_filter_#{dom_id(element)}_#{attribute}",
-
param_key: "value",
-
data_attributes: {
-
payload_data: { attribute: attribute } },
-
creatable: true
-
) do |select_component| %>
-
<%= select_component.with_toggler do %>
-
<%= react_component("Tag", {
-
text: "Add #{attribute.to_s.singularize.titleize}",
-
line: 'solid',
-
kind: "neutral",
-
tSize: "extraLarge",
-
icon: "fa-regular fa-plus",
-
cursorType: "pointer",
-
dataAttributes: {
-
action: "click->async-select-component#filterInput"
-
}
-
}) %>
-
<% end %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Array < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
then: 0
if properties[:value] != '-'
-
checked = "true"
-
else: 0
else
-
checked = "false"
-
end
-
%>
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<%= @form.check_box attribute, { class: 'w-4 h-4 text-blue-600 bg-gray-100 border-gray-base rounded focus:ring-trasparent focus:ring-0' }, '1', '0' %>
-
<%#= react_component("Input", { id: "protocol_element_#{attribute}", value: properties[:value], type: 'checkbox', size: 'tiny', name: name }) %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Boolean < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute = @attribute
-
properties = @properties
-
value = @value
-
element = @element
-
protocol = @protocol
-
edit_value = @edit_value
-
edit = @edit
-
-
id = element.id
-
-
default_custom = <<~TEXT.strip
-
<strong>Template - replace as needed</strong>
-
-
<div class="lui-text lui-text--form">
-
<label for="custom-text-#{id}">Text Custom:</label>
-
<input
-
class="lui-text--form__lui-input"
-
type="text"
-
id="custom-text-#{id}"
-
placeholder="Placeholder..."
-
value=""
-
/>
-
</div>
-
-
<script>
-
document.getElementById('custom-text-#{id}').addEventListener('change', function () {
-
const value = this.value;
-
const customEvent = new CustomEvent('protocolCustomAnswer-#{id}', {
-
detail: { value }
-
});
-
const protocolContainer = document.querySelector('[data-protocol-element-id="#{id}"]');
-
if (protocolContainer) {
-
protocolContainer.dispatchEvent(customEvent);
-
} else {
-
console.warn('Elemento com [data-protocol-element-id]="#{id}" não encontrado.');
-
}
-
});
-
</script>
-
TEXT
-
-
input_value = edit_value.presence || value.presence ||
-
then: 0
else: 0
then: 0
else: 0
(element.settings&.key?("custom") ? nil : default_custom)
-
%>
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<div class="w-full">
-
<%= render LooposUi::Inputs::TextArea.new(
-
value: input_value,
-
name: "protocol_element[custom]",
-
rows: 5
-
) %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Custom < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form:)
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<div class="relative max-w-sm">
-
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
-
<i class="fa-regular fa-calendar-days"></i>
-
</div>
-
<%= react_component("Datetime", {
-
id: "protocol_element_#{attribute}",
-
name: "protocol_element[#{attribute}]",
-
kind: "DatePicker",
-
lang:'enUS',
-
variant:"core",
-
isRange: false,
-
granularity: "date",
-
inputClass: "core-input-box core-input-container-tiny",
-
selectedDate: properties[:value]
-
}) %>
-
</div>
-
<%#= react_component("Input", { id: "protocol_element_#{attribute}", value: properties[:value], type: properties[:type], size: 'tiny', name: "protocol_element[#{attribute}]" }) %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Date < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<%= form.select attribute, options_for_select(properties[:enum]), {}, class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" %>
-
<%#= react_component("Input", { id: "protocol_element_#{attribute}", value: properties[:value], type: 'checkbox', size: 'tiny', name: "protocol_element[#{attribute}]" }) %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Enum < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
<div>
-
<span class='block mb-1 text-xs font-normal text-gray-darkest'><%= key.to_s.titleize %></span>
-
<%= turbo_frame_tag element, key do %>
-
<%= form_with model: [:admin_v2, element.becomes(::Protocol::Element)], data: { controller: "autosubmit", autosubmit_target: "form" } do |form| %>
-
<%= form.hidden_field key, value: '0' %>
-
then: 0
else: 0
<%= react_component("Toggle", { id: "protocol_element_date_#{key}", name: "protocol_element[#{key}]", checked: value ? "checked" : "false", small: true, inputDataAttributes: { action: "change->autosubmit#submit", turbo_frame: "_top" } }) %>
-
<% end %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Granularity < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<%= @form.file_field attribute, class: " protocol-input" %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Image < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%# Currently is the same as the number input %>
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
<%= render LooposUi::Protocol::Elements::Fields::Number.new(
-
element: element,
-
attribute: attribute,
-
properties: properties,
-
edit_value: edit_value)
-
%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Integer < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%# Currently is the same as the number input %>
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %> <%#TODO why submit only if label present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<% input_value = edit_value.presence || properties[:value].to_f %>
-
<% input_data_type = "number" %>
-
<%= react_component("Input", {
-
placeholder: "-",
-
id: "protocol_element_#{attribute}",
-
value: input_value,
-
type: "text", size: 'tiny',
-
name: "protocol_element[#{attribute}]",
-
dataAttributes: { "controller": "#dropkiq", "preview-id": "protocol_element_#{attribute}_preview", "input-type": input_data_type },
-
}) %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Number < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value = value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
end
-
end
-
end
-
end
-
end
-
<%
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
index = @index
-
option = @option
-
locale = I18n.locale
-
%>
-
-
<div class="flex w-full" id="<%= dom_id option %>_protocol" data-option-id="<%= option.id %>">
-
<%= @form.fields_for :select_options, option do |oform| %>
-
<div id="<%= dom_id option %>_div" data-controller="update-index-inputs" data-id="<%= dom_id option %>_protocol" data-update-index-inputs-target="div"></div>
-
<%= oform.hidden_field :active, value: true, id: "#{oform.object.id}_option_destroy" %>
-
<%= oform.hidden_field :id, value: oform.object.id, id: "#{oform.object.id}_option_id" %>
-
<div class="grid grid-cols-[calc(100%-64px)_24px_24px] gap-xs items-center w-full justify-between">
-
<div>
-
<div class="flex flex-row items center gap-2xs">
-
<%= oform.text_field :label,
-
value: option.label(locale: locale),
-
class: "protocol-input",
-
data: {
-
action: "keydown.enter->autosubmit#submit blur->autosubmit#submit"
-
} %>
-
<div class="dealbreaker element__toggle-wrapper border-0! relative">
-
<input name="protocol_element[select_options_attributes][<%= index - 1 %>][deal_breaker]" type="hidden" value="0" autocomplete="off" />
-
then: 0
else: 0
<%= react_component("Toggle", { id: "#{oform.object.id}_deal_breaker", name: "protocol_element[select_options_attributes][#{index - 1}][deal_breaker]", checked: option.deal_breaker ? "checked" : "false", small: true, inputDataAttributes: { action: "change->autosubmit#submit", turbo_frame: "_top" } }) %>
-
<span class="text-gray-base">DB</span>
-
</div>
-
</div>
-
</div>
-
<%= render LooposUi::Modal.for_delete(model_name: "Protocol Element") do |modal| %>
-
<% modal.with_trigger do %>
-
<button type="button" class="flex items-center justify-center w-[24px] h-[24px] bg-[#FFE7E7] text-general-danger-800 rounded text-[10px] fa-regular fa-trash-can"></button>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
kind: :app,
-
tag_options: {
-
id: "#{oform.object.id}_button_destroy",
-
type: 'button',
-
data: {
-
input_id: "#{oform.object.id}_option_destroy",
-
controller: "destroy-input",
-
action: "click->destroy-input#changeDestroyInput click->lui--button#startLoading click->actions_loading#run"
-
}
-
}
-
) %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Option < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form:,
-
index: , option:)
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@index = index
-
@form = form
-
@option = option
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
form = @form
-
%>
-
<% form.object = element.becomes(::Protocol::Element::Input::Select)%>
-
<div class="flex flex-col w-full gap-2">
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<div class="flex flex-col gap-2" >
-
<div id="<%= element.id %>_select_options" class="flex flex-col gap-2"
-
data-controller="sortable-options"
-
data-sortable-options-url-value="<%= helpers.main_app.update_select_option_admin_v2_protocol_elements_path %>"
-
>
-
then: 0
else: 0
then: 0
else: 0
<% if value&.any? %>
-
<% value.order(:position).each_with_index do |option, index| %>
-
then: 0
else: 0
<% if option.active %>
-
<%= render LooposUi::Protocol::Elements::Fields::Option.new(
-
attribute: attribute,
-
properties: properties,
-
index: index + 1,
-
value: value,
-
option: option,
-
element: element,
-
protocol: protocol,
-
edit_value: edit_value,
-
form: form) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<div class="h-8 w-fit flex flex-row items-center gap-2 justify-start" id="<%= element.id %>_options_collection" data-action="click->protocol-elements#createOption">
-
<i class="flex items-center gap-2 justify-center copy-14-medium text-app-800-primary fa-regular fa-plus"></i>
-
<div class="copy-14-medium text-app-800-primary"><%= t(".add_option") %></div>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class Options < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%
-
-
attribute= @attribute
-
properties= @properties
-
value= @value
-
element= @element
-
protocol= @protocol
-
edit_value= @edit_value
-
edit = @edit
-
-
%>
-
-
<div class="flex flex-col items-start w-full gap-2">
-
then: 0
else: 0
<% if properties[:label].present? %>
-
<div class="flex flex-row gap-2xs">
-
<p class="break-all text-xs font-normal text-gray-darkest"><%= properties[:label] %></p>
-
<%#= inline.render_submit_buttons(form: form) %>
-
</div>
-
<% end %>
-
<%#= attribute %>
-
<% input_value = edit_value.presence || value %>
-
<% input_data_type = "string" %>
-
<%= react_component("Input", {
-
placeholder: "-",
-
id: "protocol_element_#{attribute}",
-
value: input_value,
-
type: "text", size: 'tiny',
-
name: "protocol_element[#{attribute}]",
-
dataAttributes: { "controller": "#dropkiq", "preview-id": "protocol_element_#{attribute}_preview" , "input-type": input_data_type},
-
}) %>
-
<%#= form.text_field attribute, class: "protocol-input" %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Fields
-
1
class String < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(attribute:, properties:, value:, element:, protocol:, edit_value:, edit: false, form: )
-
@attribute= attribute
-
@properties= properties
-
@value= value
-
@element= element
-
@protocol= protocol
-
@edit_value= edit_value
-
@edit = edit
-
@form = form
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Break < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Html < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Image < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Text < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Title < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Info
-
1
class Url < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Bool < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Custom < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Date < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class DateRange < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Email < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Files < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Iban < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Identifier < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Images < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Money < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Number < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class PhoneNumber < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class PostalCode < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url) %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Select < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::Protocol::ProtocolElementComponent.new(element: @element, current_user: @current_user, catalog_node: @catalog_node, edit: @edit, title: @title, export_url: @export_url, broadcast_url: @broadcast_url)%>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Input
-
1
class Text < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= turbo_frame_tag 'protocol_sidebar' do %>
-
<%= render LooposUi::Protocol::ProtocolSidebarComponent.new do |sidebar| %>
-
<% sidebar.with_header do %>
-
<%= turbo_stream_from "protocol_sidebar_stream" %>
-
<div class="flex flex-row items-center justify-between">
-
<div class="flex flex-row items-center gap-2">
-
<i class="fa-regular fa-bars-staggered"></i>
-
<h1>Element Settings</h1>
-
</div>
-
<%#= link_to preview_admin_v2_protocol_element_path(element, catalog_node_id: catalog_node&.id ) , data: { turbo_frame: 'protocol_sidebar', "protocol_sidebar" => "close" }, class: 'btn btn-primary' do %>
-
<%# Removed the link since it does not look necessary and it changes the page %>
-
<%= link_to "#", data: { turbo_frame: 'protocol_sidebar', "protocol_sidebar" => "close" }, class: 'btn btn-primary' do %>
-
<i class="fa-solid fa-xmark text-gray-600 cursor-pointer"></i>
-
<% end %>
-
</div>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="w-full h-[49px] justify-start items-start inline-flex protocol_sidebar__section--padded">
-
<div class="w-full h-[49px] justify-start items-start flex">
-
<div class="grow shrink basis-0 h-12 p-4 <%= input_type_class %> rounded-lg justify-start items-center gap-3 flex">
-
<div class="w-4 justify-center items-center flex">
-
<div class="text-center <%= input_icon_class %> text-base font-normal font-['Font Awesome 6 Pro']">
-
<i class="<%= element.class.icon %>"></i>
-
</div>
-
</div>
-
<div class="grow shrink basis-0 h-[17px] justify-start items-center flex">
-
<div class="<%= input_icon_class %> copy-14-medium">
-
<%= type_text %>
-
</div>
-
</div>
-
<div class="w-3 justify-center items-center flex">
-
</div>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="flex flex-row items-center justify-between protocol_sidebar__section--padded">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold">Associate to apps</p>
-
<p class="text-general-gray-700 copy-12 ">Link to LoopOS apps for display.</p>
-
</div>
-
<%= turbo_frame_tag "element_apps" do %>
-
<%= render "admin/v2/protocol_elements/apps", enabled_apps: enabled_apps, element: element %>
-
<% end %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if has_settings? %>
-
<% sidebar.with_content_section do %>
-
<%= settings_gem %>
-
<% end %>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<p class="text-general-gray-900 copy-14 font-bold">Tags</p>
-
<div class="flex justify-start mt-6">
-
<%= turbo_frame_tag element, 'tags' do %>
-
<%= render 'admin/v2/tags/tags', taggable: element %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if can_edit_slug? %>
-
<% sidebar.with_content_section do %>
-
<div class="flex flex-col gap-4 protocol_sidebar__section--padded">
-
<p class="text-general-gray-900 copy-14 font-bold">Show</p>
-
<%= render "admin/v2/protocol_elements/slug_edit", element: element, protocol: element.protocol %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
class LooposUi::Protocol::Elements::Settings < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_reader :element, :presenter, :catalog_node, :current_user
-
-
1
renders_one :settings_gem
-
-
1
def initialize(element:, presenter:, catalog_node: nil, current_user:)
-
@element = element
-
@presenter = presenter
-
@catalog_node = catalog_node
-
@current_user = current_user
-
end
-
-
1
def has_settings?
-
presenter.has_settings?
-
end
-
-
1
def settings_ui_schema
-
presenter.settings_ui_schema
-
end
-
-
1
def settings_schema
-
presenter.settings_schema
-
end
-
-
1
def enabled_apps
-
presenter.enabled_apps
-
end
-
-
1
def input_type_class
-
then: 0
else: 0
presenter.input? ? "bg-red-50" : "bg-apps-hubs-200"
-
end
-
-
1
def input_icon_class
-
then: 0
else: 0
presenter.input? ? "text-orange-700" : "text-apps-hubs-800-primary"
-
end
-
-
1
def type_text
-
presenter.type.split.first
-
end
-
-
1
def update_url
-
then: 0
else: 0
"/protocol_elements/#{element.id}/update_settings?catalog_node_id=#{catalog_node&.id}"
-
end
-
-
1
def can_edit_slug?
-
current_user.can_edit_protocol_element_slug?
-
end
-
end
-
<%
-
element = @element
-
presenter = element.presenter
-
protocol = element.protocol
-
catalog_node ||= @catalog_node || nil
-
current_user = @current_user
-
%>
-
<%= turbo_frame_tag element do %>
-
then: 0
else: 0
<div data-controller="protocol-elements" data-protocol-elements-element-value="<%= dom_id(element)%>" data-protocol-elements-id-value="<%= element.id %>" data-protocol-elements-node-value="<%= catalog_node&.id %>">
-
then: 0
else: 0
<div class="relative py-4 cursor-pointer" style="margin: 80px 0px;" data-action="<%= !@edit ? "click->protocol-elements#edit" : "" %>"
-
>
-
then: 0
else: 0
<% if current_user.can_edit_protocols? %>
-
-
then: 0
<% if @edit %>
-
<%= link_to presenter.preview_path(catalog_node) , data: { turbo_frame: "protocol_sidebar", 'protocol-elements-target': "preview" }, class: 'hidden' do%>
-
<% end %>
-
<%= link_to presenter.show_path(catalog_node) , data: { turbo_frame: element, 'protocol-elements-target': "preview" }, class: 'hidden' do%>
-
<% end %>
-
else: 0
<% else %>
-
<%= link_to presenter.settings_path(catalog_node) , data: { turbo_frame: "protocol_sidebar", 'protocol-elements-target': "button" }, class: 'hidden' do%>
-
<% end %>
-
<%= link_to presenter.edit_path(catalog_node) , data: { turbo_frame: element, 'protocol-elements-target': "button" }, class: 'hidden' do%>
-
<% end %>
-
<% end %>
-
-
<div class="element__number hidden!"><%= presenter.position %></div>
-
<div class="z-10 absolute top-1/2 left-0 transform -translate-y-1/2 w-[24px] h-[24px] rounded items-center! flex! justify-center! cursor-move handle" style="border-radius: 8px;
-
border: 1px solid var(--Colors-General-Grays-300, #DEE2E6);
-
background: var(--Colors-General-Grays-100, #F8F9FA);">
-
<i class="fa-sharp fa-regular text-xs fa-grip-dots-vertical"></i>
-
</div>
-
<% end %>
-
then: 0
else: 0
<hr class="absolute <%= @edit ? "border-t-2" : "border-t" %> border-gray-900 border-dashed" style="width: calc(100% + 122px); margin-left: -66px;">
-
<div class="absolute right-0 top-1/2 transform -translate-y-1/2 flex flex-row gap-2">
-
then: 0
else: 0
<% if @edit %>
-
then: 0
else: 0
<% if presenter.input? %>
-
<div class="element__toggle-wrapper">
-
<span class="copy-12 text-black">Mark as required</span>
-
<%= turbo_frame_tag element, :required do %>
-
then: 0
else: 0
<% if current_user.can_edit_protocols? %>
-
<%= form_with model: [:admin_v2, element.becomes(::Protocol::Element)], data: { controller: "autosubmit", autosubmit_target: "form" } do |form| %>
-
<input name="protocol_element[required]" type="hidden" value="0" autocomplete="off">
-
then: 0
else: 0
<%= react_component("Toggle", { id: "protocol_element_required", name: "protocol_element[required]", checked: presenter.required ? "checked" : "false", small: true, inputDataAttributes: { action: "change->autosubmit#submit", turbo_frame: "_top" } }) %>
-
then: 0
else: 0
<%= form.hidden_field :catalog_node_id, value: @catalog_node&.id %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<%= render LooposUi::Modal.for_duplicate(model_name: "Protocol Element") do |modal| %>
-
<% modal.with_trigger do %>
-
<button type="button"
-
class="bg-[#F5F5F5] w-[24px] h-[24px] rounded items-center flex justify-center text-[10px] fa-kit fa-regular-copy-circle-plus text-[#5F5F5F]"
-
data-toggle="tooltip"
-
title="Duplicate protocol element"
-
></button>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
href: duplicate_element_admin_v2_protocol_element_path(element),
-
kind: :app,
-
tag_options: { data: { "turbo-method": :post, type: "ignore", action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
<%= render LooposUi::Modal.for_delete(model_name: "Protocol Element") do |modal| %>
-
<% modal.with_trigger do %>
-
<button type="button" class="flex items-center justify-center w-[24px] h-[24px] bg-[#FFE7E7] text-general-danger-800 rounded text-[10px] fa-regular fa-trash-can"></button>
-
<% end %>
-
<% modal.with_custom_content do %>
-
<%= form_with(
-
model: [:admin_v2, element.becomes(::Protocol::Element)],
-
id: "#{element.id}",
-
class: "flex items-center justify-center w-fit h-fit",
-
method: "delete",
-
data: { turbo_frame: "#{dom_id(protocol)}_protocol_elements" }) do |form| %>
-
then: 0
else: 0
<%= form.hidden_field :catalog_node_id, name: :catalog_node_id, value: catalog_node&.id %>
-
<% end %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
kind: :app,
-
tag_options: {
-
form: "#{element.id}",
-
type: "submit",
-
data: {
-
action: "click->lui--button#startLoading click->actions_loading#run"
-
}
-
}
-
) %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module Protocol
-
1
module Elements
-
1
module Structure
-
1
class Divider < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(element:, catalog_node: nil, current_user:, edit: false, title:, export_url: nil,
-
broadcast_url: nil)
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
-
end
-
end
-
end
-
end
-
end
-
<%= turbo_frame_tag 'protocol_sidebar' do %>
-
<%= turbo_stream_from "protocol_sidebar_stream" %>
-
<%= render LooposUi::Protocol::ProtocolSidebarComponent.new do |sidebar| %>
-
<% sidebar.with_header do %>
-
<h1>What are Protocols?</h1>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<span class="copy-14-medium text-gray-900"><%= t(".a") %></span>
-
<span class="copy-14 font-bold text-gray-900"><%= t(".protocol") %></span>
-
<span class="copy-14-medium text-gray-900"><%= t(".description") %></span>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
class LooposUi::Protocol::Preview < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize
-
end
-
end
-
then: 0
else: 0
<% if defined?(@filter) %>
-
<%= render LooposUi::Protocol::ProtocolFilterComponent.new(filter: @filter, disabled_protocols: @disabled_protocols, catalog_node: @catalog_node, protocol_show: @protocol_show, protocol: @protocol)%>
-
<% end %>
-
<div class="protocol-builder">
-
<%= elements_section %>
-
<%= settings_section %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolBuilderComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :elements_section, "LooposUi::Protocol::ProtocolElementsSectionComponent"
-
1
renders_one :settings_section, "LooposUi::Protocol::ProtocolSettingsSectionComponent"
-
-
1
def initialize(filter: nil, disabled_protocols: "checked", catalog_node: nil, protocol_show: false, protocol: nil)
-
@filter = filter
-
@disabled_protocols = disabled_protocols
-
@catalog_node = catalog_node
-
@protocol_show = protocol_show
-
@protocol = protocol
-
end
-
end
-
-
1
class ProtocolElementsSectionComponent < ViewComponent::Base
-
1
def initialize
-
end
-
-
1
def call
-
content
-
end
-
end
-
-
1
class ProtocolSettingsSectionComponent < ViewComponent::Base
-
1
def initialize
-
end
-
-
1
def call
-
content
-
end
-
end
-
end
-
end
-
<%
-
protocol = @protocol
-
current_user = @current_user
-
presenter = protocol.presenter
-
elements ||= presenter.elements
-
readonly||= @readonly ||= false
-
current_catalog_node ||= @current_catalog_node ||= nil
-
show_opened ||= params[:show_opened] == "true"
-
apn = presenter.applicable_catalog_node_protocol(current_catalog_node)
-
then: 0
else: 0
show_inheritance_path = current_catalog_node.present? && protocol.catalog_node_ids.exclude?(current_catalog_node&.id)
-
%>
-
-
<%= turbo_frame_tag "#{turbo_id(protocol)}", data: {id: protocol.id } do %>
-
<%= turbo_stream_from(turbo_stream_id(protocol)) %>
-
<div class="flex">
-
then: 0
else: 0
<% if !show_inheritance_path %>
-
<div class="protocol-component__grip-wrapper">
-
<div class="protocol-component__grip">
-
<i class="protocol-component__grip-icon text-base fa-regular fa-grip-dots"></i>
-
</div>
-
</div>
-
<% end %>
-
then: 0
else: 0
<div class="protocol-component <%= show_inheritance_path.present? ? "": "protocol-component--open" %>"
-
data-accordion="open"
-
data-sortable-protocol-target="applicableCatalogNodeProtocol"
-
then: 0
else: 0
data-id="<%= apn&.protocol_id %>"
-
data-controller="protocol-accordion"
-
data-protocol-accordion-target="wrapper"
-
then: 0
else: 0
data-position="<%= apn&.position %>"
-
>
-
<%= render LooposUi::Protocol::ProtocolHeader.new( protocol: protocol, readonly: readonly, current_catalog_node: current_catalog_node, show_opened: show_opened, current_user: current_user ) %>
-
then: 0
else: 0
<div class="protocol-component__elements-wrapper <%= current_catalog_node.present? && !show_opened ? "hidden" : "" %>" id="accordion-collapse-<%= dom_id(protocol) %>"
-
aria-labelledby="accordion-collapse-<%= dom_id(protocol) %>-header">
-
<div id="<%= dom_id(protocol) %>_protocol_elements" class="protocol-component__elements-container" data-controller="sortable" data-sortable-target="main"
-
then: 0
else: 0
data-id="<%= protocol.id %>" data-catalog-node-id="<%= current_catalog_node&.id %>">
-
<%= elements_list %>
-
</div>
-
then: 0
else: 0
<% if current_user.can_edit_protocols? %>
-
<%= render LooposUi::Protocol::AddElement::AddElementComponent.new(
-
filter_path: filter_element_options_admin_v2_protocol_path(protocol.id),
-
assign_path: assign_element_admin_v2_protocol_path(protocol.id),
-
import_path: import_admin_v2_protocol_elements_path,
-
filter_wrapper_id: "protocol_elements_add_#{dom_id(protocol)}",
-
param_key: "element_type",
-
data_attributes: {
-
assign_method: "POST",
-
payload_data: {
-
# element_position: elements.size + 1,
-
protocol_id: protocol.id,
-
then: 0
else: 0
catalog_node_id: current_catalog_node&.id,
-
}
-
},
-
) %>
-
<% end %>
-
then: 0
else: 0
<% if readonly %>
-
<div class="absolute top-0 left-0 w-full h-full cursor-not-allowed z-50">
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
-
<% end %>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
renders_one :elements_list
-
-
1
def initialize(protocol:, readonly:, current_catalog_node:, current_user:)
-
@protocol= protocol
-
@readonly= readonly
-
@current_catalog_node = current_catalog_node
-
@current_user = current_user
-
end
-
end
-
-
1
class ProtocolHeader < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
def initialize(protocol:, readonly:, current_catalog_node:, show_opened:, current_user:)
-
@protocol= protocol
-
@readonly= readonly
-
@current_catalog_node= current_catalog_node
-
@current_user = current_user
-
@show_opened = show_opened
-
end
-
end
-
end
-
end
-
<%
-
element = @element
-
then: 0
else: 0
presenter = element&.presenter
-
then: 0
else: 0
protocol = element&.protocol
-
catalog_node ||= @catalog_node || nil
-
current_user = @current_user
-
params = @params
-
locale = I18n.locale
-
%>
-
then: 0
<% if @loading %>
-
<div class="h-[120px] rounded-xl bg-gray-300 bg-linear-to-r from-gray-300 to-gray-500 animate-gradient border border-gray-300 mb-4"></div>
-
else: 0
<% else %>
-
<%= turbo_frame_tag element do %>
-
<div
-
data-controller="protocol-elements"
-
data-protocol-elements-element-value="<%= dom_id(element)%>"
-
data-protocol-elements-id-value="<%= element.id %>"
-
then: 0
else: 0
data-protocol-elements-node-value="<%= catalog_node&.id %>">
-
then: 0
else: 0
then: 0
else: 0
<div class="protocol-element group <%= @edit ? "protocol-element--selected": "" %> <%= presenter.input? ? "protocol-element--input" : "protocol-element--info" %>"
-
then: 0
else: 0
data-action="<%= !@edit ? "click->protocol-elements#edit" : ""%>">
-
then: 0
else: 0
<% if current_user.can_edit_protocols? %>
-
then: 0
<% if @edit %>
-
<%= link_to presenter.preview_path(catalog_node) , data: { turbo_method: :get,turbo_frame: "protocol_sidebar", 'protocol-elements-target': "preview" }, class: 'hidden' do%>
-
<% end %>
-
<%= link_to presenter.show_path(catalog_node) , data: { turbo_method: :get,turbo_frame: element, 'protocol-elements-target': "preview show" }, class: 'hidden' do%>
-
<% end %>
-
else: 0
<% else %>
-
<%= link_to presenter.settings_path(catalog_node) , data: { turbo_method: :get,turbo_frame: "protocol_sidebar", 'protocol-elements-target': "button" }, class: 'hidden' do%>
-
<% end %>
-
<%= link_to presenter.edit_path(catalog_node) , data: { turbo_method: :get,turbo_frame: element, 'protocol-elements-target': "button" }, class: 'hidden' do%>
-
<% end %>
-
<% end %>
-
<div class="element__number hidden!"><%= presenter.position %></div>
-
then: 0
else: 0
<% if !@edit %>
-
<div class="absolute top-1/2 left-0 transform -translate-y-1/2 w-[24px] h-[24px] rounded items-center flex justify-center opacity-0 group-hover:opacity-100 cursor-move handle">
-
<i class="fa-sharp fa-regular text-xs fa-grip-dots-vertical"></i>
-
</div>
-
<% end %>
-
<% end %>
-
<div class="protocol-element__wrapper">
-
<div class="protocol-element__header">
-
<div class="protocol-element__header-left">
-
then: 0
<% if @edit %>
-
<div class="
-
then: 0
else: 0
<% if presenter.input? %> protocol-element__input
-
<% else %> protocol-element__info
-
<% end %>">
-
<i class="copy-12 <%= presenter.icon%>"></i>
-
</div>
-
<span class="copy-14-medium text-black!"><%= presenter.type %></span>
-
else: 0
<% else %>
-
then: 0
else: 0
<% if !presenter.structure? %>
-
<div class="relative w-full">
-
<div class="absolute top-0 left-0 h-full w-full z-2 cursor-pointer"></div>
-
<div class="opacity-70">
-
<%= react_component("ProtocolPreview", { jsonData: element.presenter.json_data}) %>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
<div class="protocol-element__header-right">
-
then: 0
<% if @edit %>
-
then: 0
else: 0
<% if presenter.input? %>
-
<div class="element__toggle-wrapper">
-
<span class="copy-12-medium text-black! w-max"><%= t(".mark_as_required") %></span>
-
<%= turbo_frame_tag element, :required do %>
-
then: 0
else: 0
<% if current_user.can_edit_protocols? %>
-
<%= form_with model: [:admin_v2, element.becomes(::Protocol::Element)], data: { controller: "autosubmit", autosubmit_target: "form" } do |form| %>
-
<input name="protocol_element[required]" type="hidden" value="0" autocomplete="off">
-
then: 0
else: 0
<%= react_component("Toggle", { id: "protocol_element_required", name: "protocol_element[required]", checked: presenter.required ? "checked" : "false", small: true, inputDataAttributes: { action: "change->autosubmit#submit", turbo_frame: "_top" } }) %>
-
then: 0
else: 0
<%= form.hidden_field :catalog_node_id, value: @catalog_node&.id %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<div data-controller="clipboard">
-
<button data-action="click->clipboard#copy" data-clipboard-export-param="<%= @export_url %>" data-clipboard-broadcast-param="<%= @broadcast_url %>"
-
class="bg-[#F5F5F5] w-[24px] h-[24px] rounded items-center flex justify-center text-[10px] fa-regular fa-copy text-[#5F5F5F]"
-
data-toggle="tooltip"
-
title="<%= t('.copy_to_clipboard') %>"
-
></button>
-
</div>
-
<div data-controller="modal">
-
<%= render LooposUi::Modal.for_duplicate(model_name: "Element") do |modal| %>
-
<% modal.with_trigger do %>
-
<button data-action="click->modal#open"
-
class="bg-[#F5F5F5] w-[24px] h-[24px] rounded items-center flex justify-center text-[10px] fa-kit fa-regular-copy-circle-plus text-[#5F5F5F]"
-
data-toggle="tooltip"
-
title="<%= t('.duplicate_protocol_element') %>"
-
></button>
-
<% end %>
-
then: 0
else: 0
<% modal.with_primary_action(text: t(".confirm"), href: duplicate_element_admin_v2_protocol_element_path(element, catalog_node_id: @catalog_node&.id), tag_options: { type: "submit", "data-turbo-method": :post, turbo_frame: "_top", action: "click->protocol-elements#resetWindowLocks" }) %>
-
<% end %>
-
</div>
-
<%= render LooposUi::Modal.for_delete(model_name: "Protocol Element") do |modal| %>
-
<% modal.with_trigger do %>
-
<button type="button" class="flex items-center justify-center w-[24px] h-[24px] bg-[#FFE7E7] text-general-danger-800 rounded text-[10px] fa-regular fa-trash-can"></button>
-
<% end %>
-
<% modal.with_custom_content do %>
-
<%= form_with(
-
model: [:admin_v2, element.becomes(::Protocol::Element)],
-
id: "#{element.id}",
-
class: "flex items-center justify-center w-fit h-fit",
-
method: "delete",
-
data: { turbo_frame: "#{dom_id(protocol)}_protocol_elements", "protocol-elements-target": "deleteForm" }) do |form| %>
-
then: 0
else: 0
<%= form.hidden_field :catalog_node_id, name: :catalog_node_id, value: catalog_node&.id %>
-
<% end %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
kind: :app,
-
tag_options: {
-
form: "#{element.id}",
-
type: "submit",
-
data: {
-
action: "click->lui--button#startLoading click->actions_loading#run"
-
}
-
}
-
) %>
-
<% end %>
-
else: 0
<% else %>
-
<% presenter.enabled_apps.each do |app, enabled| %>
-
then: 0
<% if app.to_s == "submission_extra" && enabled %>
-
<div class="relative inline-block">
-
<%= react_component("LogoApp", {
-
app: "submission",
-
size: "small",
-
icon: true
-
}) %>
-
<div class="absolute bottom-[15px] left-[15px] w-3 h-3">
-
<%= image_tag('submission-extra-arrow-core.svg', width: '12', height: '12') %>
-
</div>
-
else: 0
</div>
-
then: 0
else: 0
<% elsif enabled %>
-
<%= react_component("LogoApp", {
-
app: app,
-
size: "small",
-
icon: true
-
}) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
then: 0
else: 0
<% if @edit %>
-
then: 0
else: 0
<div class="protocol-element__content <%= presenter.input? ? "protocol-element__content--input" : "protocol-element__content--info" %>">
-
<%= form_with(model: [:admin_v2, @element.becomes(::Protocol::Element)], id:"#{dom_id(element)}" , class: "w-full", data: { controller: "autosubmit", "autosubmit-target": "form", turbo_frame: dom_id(element) }) do |form| %>
-
<%
-
element = @element
-
presenter = element.presenter
-
schema = presenter.left_settings_schema
-
protocol = element.protocol
-
-
%>
-
<%# label %>
-
<% edit_value = element.untranslated_label_html(locale: locale).presence || element.untranslated_label(locale: locale).to_s %>
-
<div class='w-full'>
-
then: 0
<% if @element.is_a?(Protocol::Element::Info::Html) %>
-
<%= render LooposUi::Inputs::TextArea.new(
-
value: edit_value,
-
name: "protocol_element[untranslated_label_html]",
-
rows: 5
-
) %>
-
else: 0
<% else %>
-
<%= render(LooposUi::Wysiwyg.new(initial_value: edit_value, input_name: "protocol_element[untranslated_label_html]", small: true, app: "neutral")) %>
-
<% end %>
-
<%#= form.text_field :label, class: "protocol-input", data: { controller: "#dropkiq", "preview-id": "left-setting-label-#{form.object.id}-preview" }, value: edit_value %>
-
then: 0
else: 0
<%= form.hidden_field "catalog_node_id", value: @catalog_node&.id %>
-
<div id="left-setting-label-<%= form.object.id %>-preview" class='dropkiq-preview'></div>
-
</div>
-
<%# placeholder%>
-
<% if false %>
-
<% edit_value = form.object[:placeholder] %>
-
<div class='w-full'>
-
<%= form.text_field :placeholder, class: "protocol-input", data: { controller: "#dropkiq", "preview-id": "left-setting-placeholder-#{form.object.id}-preview" }, value: edit_value %>
-
<%= form.hidden_field "catalog_node_id", value: @catalog_node&.id %>
-
<div id="left-setting-placeholder-<%= form.object.id %>-preview" class='dropkiq-preview'></div>
-
</div>
-
<% end %>
-
<% schema[:properties].each do |attribute, properties| %>
-
<%
-
specific_component = "LooposUi::Protocol::Elements::Fields::#{properties[:type].camelize}".constantize
-
-
then: 0
else: 0
default_value = element.respond_to?(attribute) ? element.send(attribute) : nil
-
translatable_setting = element.class.respond_to?(:translatable_settings) && element.class.translatable_settings.include?(attribute.to_s)
-
-
then: 0
value = if translatable_setting && element.respond_to?("#{attribute}_#{locale}")
-
element.send("#{attribute}_#{locale}")
-
else: 0
else
-
default_value
-
end
-
-
then: 0
else: 0
settings_key = translatable_setting ? "#{attribute}_#{locale}" : attribute
-
edit_value = element.settings_hash.dig(settings_key.to_s) || value
-
-
properties[:value] = value
-
%>
-
<%= render specific_component.new(
-
attribute: attribute,
-
properties: properties,
-
value: value,
-
element: element,
-
protocol: protocol,
-
edit_value: edit_value,
-
form: form) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolElementComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
include LooposUi::InlineEditComponent
-
-
1
renders_one :editable_content
-
-
1
def initialize(loading: false, element: nil, catalog_node: nil, current_user: nil, edit: false, title: nil,
-
export_url: nil, broadcast_url: nil)
-
@loading= loading
-
@element= element
-
@current_user = current_user
-
@catalog_node = catalog_node
-
@edit = edit
-
@title = title
-
@export_url = export_url
-
@broadcast_url = broadcast_url
-
end
-
end
-
end
-
end
-
<%
-
# Extract the keys from `filter` that have `true` values
-
filter = @filter.map(&:to_s)
-
# Add additional keys to check
-
apps = ::Protocol::Element.enabled_apps.keys + [:link]
-
apps = apps.map(&:to_s)
-
then: 0
if @protocol_show
-
highlight_url = highlight_admin_v2_protocol_path(@protocol)
-
form_url = filter_elements_admin_v2_protocol_path(@protocol)
-
else: 0
else
-
disabled_protocols = @disabled_protocols
-
then: 0
else: 0
disabled_protocols_url = @catalog_node.catalogable_type == "Catalog::Product" ? toggle_protocols_admin_v2_catalog_product_path(@catalog_node.catalogable_id) : toggle_protocols_admin_v2_catalog_category_path(@catalog_node.catalogable_id)
-
then: 0
else: 0
form_url = @catalog_node.catalogable_type == "Catalog::Product" ? filter_protocols_admin_v2_catalog_product_path(@catalog_node.catalogable_id) : filter_protocols_admin_v2_catalog_category_path(@catalog_node.catalogable_id)
-
paste_configuration_copy = t(".paste_config_details", element: @catalog_node.catalogable_type.split("::").second)
-
end
-
%>
-
<div class="protocol-filter mb-8">
-
<div class="flex flex-row items-center justify-between">
-
<div class="flex flex-row items-center justify-center gap-2">
-
then: 0
<% if @protocol_show %>
-
<p class="copy-14-medium text-app-800-primary"><%= t(".catalog_section") %></p>
-
<%= form_tag(highlight_url, method: :put, remote: false, class: "contents", id: 'protocol_highlight', data: { turbo_frame: '_top', controller: "autosubmit", autosubmit_target: "form" }, multipart: true) do |form| %>
-
<%= react_component(
-
"Toggle",
-
{
-
name: "protocol[highlight]",
-
then: 0
else: 0
checked: @protocol.highlight == true ? 'checked' : false,
-
dataAttributes:{ controller: "loading", "loading-target": "toggle"},
-
inputDataAttributes: {
-
action: "change->autosubmit#submit change->loading#setToggleLoading"
-
},
-
}
-
)
-
%>
-
<% end %>
-
else: 0
<% else %>
-
<p class="copy-14-medium text-app-800-primary"><%= t(".show_disabled") %></p>
-
<%= form_tag(disabled_protocols_url, method: :get, remote: false, class: "contents", id: 'applicable_catalog_node_protocol_form', data: { turbo_stream: true, controller: "autosubmit", autosubmit_target: "form" }, multipart: true) do |form| %>
-
<%= react_component(
-
"Toggle",
-
{
-
id: "disabled_protocols",
-
name: "disabled_protocols",
-
then: 0
else: 0
checked: disabled_protocols == 'true' ? 'checked' : 'false',
-
dataAttributes:{ controller: "loading", "loading-target": "toggle"},
-
inputDataAttributes: {
-
action: "change->autosubmit#submit change->loading#setToggleLoading"
-
},
-
}
-
)
-
%>
-
<% end %>
-
<% end %>
-
</div>
-
<div class="flex flex-row items-center gap-4">
-
<p class="copy-14-medium text-app-800-primary"><%= t(".view_by") %></p>
-
<div class="flex flex-row items-center gap-2">
-
<% apps.each do |app| %>
-
<%= form_with(url: form_url) do |form| %>
-
<% values = filter.dup %>
-
then: 0
else: 0
<% values.include?(app) ? values.delete(app) : values.append(app) %>
-
<%= form.button nil, value: false, name: :turbo_stream do %>
-
<% values.each do |v| %>
-
<%= form.hidden_field :enabled_apps, multiple: true, value: v %>
-
<% end %>
-
<div class="relative inline-block">
-
then: 0
<% if app == "submission_extra" %>
-
<%= react_component("AppButton", {
-
app: "submission",
-
size: "small",
-
active: !filter.include?(app)
-
}) %>
-
<div class="absolute top-px right-px">
-
<%= image_tag('submission-extra-arrow-core.svg', width: '12', height: '12') %>
-
</div>
-
else: 0
<% else %>
-
<%= react_component("AppButton", {
-
app: app,
-
size: "small",
-
active: !filter.include?(app)
-
}) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
then: 0
else: 0
<% if @protocol_show == false %>
-
<div data-controller="modal clipboard" class="flex flex-row gap-1">
-
<% copy_configuration = capture do %>
-
<%= react_component("Button", { text: "", app: "core", icon: "fa-kit fa-regular-copy-gear", variant: "secondary", size: "large", iconPosition: "left", dataAttributes: { 'action': "click->clipboard#copy", 'clipboard-export-param': export_protocols_configuration_admin_v2_catalog_node_path(@catalog_node), 'clipboard-broadcast-param': broadcast_copied_protocols_configuration_admin_v2_catalog_nodes_path } }) %>
-
<% end %>
-
<%= react_component("Tooltip", { text: t(".copy_configuration"), children: copy_configuration, placement: "top" }) %>
-
<%= render LooposUi::Modal.new(
-
title: t(".confirm_paste_configuration"),
-
description: raw(paste_configuration_copy)
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<% paste_configuration = capture do %>
-
<%= react_component("Button", { text: "", app: "core", icon: "fa-kit fa-regular-paste-gear", variant: "secondary", size: "large", iconPosition: "left", dataAttributes: { "loading-target": "toggle" } }) %>
-
<% end %>
-
<%= react_component("Tooltip", { text: t(".paste_configuration"), children: paste_configuration, placement: "top" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t('.paste'),
-
kind: :app,
-
tag_options: {
-
data: {
-
action: "click->clipboard#paste click->lui--button#startLoading click->actions_loading#run",
-
"clipboard-import-param": import_protocols_configuration_admin_v2_catalog_node_path(@catalog_node),
-
"clipboard-node-param": @catalog_node.id,
-
"clipboard-refresh-param": "true"
-
}
-
}
-
) %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolFilterComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
def initialize(filter: nil, disabled_protocols: nil, catalog_node: nil, protocol_show: false, protocol: nil)
-
@filter = filter
-
@disabled_protocols = disabled_protocols
-
@catalog_node = catalog_node
-
@protocol_show = protocol_show
-
@protocol = protocol
-
end
-
end
-
end
-
end
-
<%
-
protocol= @protocol
-
readonly||= @readonly ||= false
-
current_catalog_node ||= @current_catalog_node ||= nil
-
current_user = @current_user
-
protocol_presenter = protocol.presenter
-
show_inheritance_path = current_catalog_node.present? && protocol.catalog_node_ids.exclude?(current_catalog_node.id)
-
inherited_protocol_path ||= show_inheritance_path && protocol.inheritance_path(current_catalog_node)
-
applicable_catalog_node_protocol ||= protocol_presenter.applicable_catalog_node_protocol(current_catalog_node)
-
show_opened ||= @show_opened ||= false
-
export_modal_protocol_id = "export_protocol_#{dom_id(protocol)}"
-
duplicate_modal_protocol_id = "duplicate_protocol_#{dom_id(protocol)}"
-
%>
-
-
then: 0
else: 0
<div class="protocol-component__header <%= show_inheritance_path.present? ? "": "protocol-component__header--open" %>" data-protocol-accordion-target="header">
-
<div class="protocol-component__header-content">
-
<div class="protocol-component__header-left">
-
then: 0
else: 0
<div class="protocol-component__trigger--close <%= show_inheritance_path.present? ? "rotate-[0]!": "" %>" id="#accordion-collapse-<%= dom_id(protocol)%>-header"
-
data-accordion-target="#accordion-collapse-<%= dom_id(protocol)%>"
-
data-action="click->protocol-accordion#toggleClass"
-
then: 0
else: 0
aria-expanded="<%= show_inheritance_path.present? ? "false": "true" %>"
-
aria-controls="accordion-collapse-<%= dom_id(protocol)%>">
-
<svg data-accordion-icon class="w-3 h-3 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
-
then: 0
else: 0
<path xmlns="http://www.w3.org/2000/svg" d="M10.375 1.42188L5.89844 5.71094C5.75781 5.82812 5.61719 5.875 5.5 5.875C5.35938 5.875 5.21875 5.82812 5.10156 5.73438L0.601562 1.42188C0.367188 1.21094 0.367188 0.835938 0.578125 0.625C0.789062 0.390625 1.16406 0.390625 1.375 0.601562L5.5 4.53906L9.60156 0.601562C9.8125 0.390625 10.1875 0.390625 10.3984 0.625C10.6094 0.835938 10.6094 1.21094 10.375 1.42188Z" fill="<%= show_inheritance_path.present? ? "#212529": "#B53C00" %>"/>
-
</svg>
-
</div>
-
<span class="protocol-component__name copy-14-medium" id="<%= dom_id(protocol)%>_name">
-
<%= protocol.name %>
-
</span>
-
</div>
-
then: 0
<% if applicable_catalog_node_protocol.present? %>
-
<div class="protocol-component__header-right">
-
then: 0
else: 0
<% if show_inheritance_path %>
-
<% icon = capture do %>
-
<%= link_to protocol_presenter.link_to_attached_node(current_catalog_node), remote: false, data: { turbo_frame: '_top' } do %>
-
<div class="protocol-component__header-inheritance copy-12 tooltip-wrapper">
-
<span class="flex">
-
<i class="fa-regular fa-diagram-nested text-app-800-primary"></i>
-
</span>
-
<span class="protocol-component__header-inheritance-title copy-12 font-bold underline text-app-800-primary"><%= inherited_protocol_path %></span>
-
</div>
-
<% end %>
-
<% end %>
-
<%= react_component("Tooltip", { text: "#{t(".inherited_from")} <a href='#{protocol_presenter.link_to_attached_node(current_catalog_node)}'>#{inherited_protocol_path}</a>" , children: icon, placement: "top" }) %>
-
<% end %>
-
then: 0
else: 0
<% if readonly && current_user.can_edit_protocols? %>
-
<div class="form-layout__status">
-
<%# FIXME: This should not reload the page %>
-
<%= form_tag(active_for_catalog_node_admin_v2_protocol_path(protocol.id), method: :put, remote: false, class: "contents", id: 'applicable_catalog_node_protocol_form', data: { turbo_frame: '_top', controller: "autosubmit", autosubmit_target: "form" }, multipart: true) do |form| %>
-
<input name="referrer" type="hidden" value="<%= request.original_url %>" autocomplete="off">
-
<input name="applicable_catalog_node_protocol[id]" type="hidden" value="<%= applicable_catalog_node_protocol.id %>" autocomplete="off">
-
<input name="applicable_catalog_node_protocol[active]" type="hidden" value="0" autocomplete="off">
-
<%= react_component(
-
"Toggle",
-
{
-
id: "applicable_catalog_node_protocol_active",
-
name: "applicable_catalog_node_protocol[active]",
-
then: 0
else: 0
checked: applicable_catalog_node_protocol.active ? "checked" : "false",
-
dataAttributes:{ controller: "loading", "loading-target": "toggle"},
-
inputDataAttributes: {
-
then: 0
else: 0
action: current_user.can_edit_protocol_inheritance? ? "change->autosubmit#submit change->loading#setToggleLoading" : "",
-
turbo_frame: "_top"
-
},
-
blocked: !current_user.can_edit_protocol_inheritance?,
-
}
-
)
-
%>
-
<% end %>
-
</div>
-
<% end %>
-
-
then: 0
else: 0
<% if !show_inheritance_path %>
-
<%= form_tag(highlight_admin_v2_protocol_path(protocol), method: :put, remote: false, class: "contents", id: 'protocol_highlight', data: { turbo_frame: '_top', controller: "autosubmit", autosubmit_target: "form" }, multipart: true) do |form| %>
-
<input name="protocol[highlight]" type="hidden" value="<%= !protocol.highlight %>" autocomplete="off">
-
then: 0
else: 0
<input name="protocol[catalog_node_id]" type="hidden" value="<%= current_catalog_node&.id %>" autocomplete="off">
-
<% move = capture do %>
-
then: 0
else: 0
<button class=""><i class="fa-regular <%= protocol.highlight ? "fa-arrow-down-from-bracket": "fa-arrow-up-from-bracket" %> text-app-800-primary cursor-pointer"></i></button>
-
<% end %>
-
then: 0
else: 0
<%= react_component("Tooltip", { text: protocol.highlight ? t(".move_to_main") : t(".move_to_catalog") , children: move, placement: "top" }) %>
-
<% end %>
-
then: 0
<% if readonly && current_user.can_edit_protocols? %>
-
<i class="fa-regular fa-clone text-gray-400"></i>
-
<i class="fa-regular fa-trash-alt text-gray-400"></i>
-
else: 0
<% else %>
-
<% settings = capture do %>
-
then: 0
else: 0
<%= link_to settings_admin_v2_protocol_path(protocol, catalog_node_id: current_catalog_node.present? ? current_catalog_node.id : nil) , data: { turbo_frame: 'lui-main-layout-drawer_bar', action: "click->protocol-accordion#moveSidebar click->protocol-accordion#selectedProtocol" }, class: 'btn btn-primary' do %>
-
<i class="fa-regular fa-gear text-app-800-primary cursor-pointer"></i>
-
<% end %>
-
<% end %>
-
<%= react_component("Tooltip", { text: t(".open_settings") , children: settings, placement: "top" }) %>
-
<%= render LooposUi::Modal.for_export(model_name: "Protocol", id: export_modal_protocol_id) do |modal| %>
-
<% export = capture do %>
-
<i class="fa-regular fa-download text-app-800-primary cursor-pointer"></i>
-
<% end %>
-
<% modal.with_trigger do %>
-
<%= react_component("Tooltip", { text: t(".export_protocol") , children: export, placement: "top" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
href: export_admin_v2_protocol_path(protocol, format: :json),
-
kind: :app,
-
tag_options: { data: { action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
<div data-controller="clipboard">
-
<% copy = capture do %>
-
<i class="fa-regular fa-copy text-app-800-primary cursor-pointer" data-action="click->clipboard#copy" data-clipboard-export-param="<%= export_admin_v2_protocol_path(protocol) %>" data-clipboard-broadcast-param="<%= broadcast_copied_protocol_admin_v2_protocols_path %>"></i>
-
<% end %>
-
<%= react_component("Tooltip", { text: t(".copy_protocol") , children: copy, placement: "top" }) %>
-
</div>
-
<%= render LooposUi::Modal.for_duplicate(model_name: "Protocol", id: duplicate_modal_protocol_id) do |modal| %>
-
<% clone = capture do %>
-
<i class="fa-kit fa-regular-copy-circle-plus text-app-800-primary cursor-pointer"></i>
-
<% end %>
-
<% modal.with_trigger do %>
-
<%= react_component("Tooltip", { text: t(".duplicate_protocol") , children: clone, placement: "top" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
href: duplicate_admin_v2_protocol_path(protocol, catalog_node_id: current_catalog_node.id),
-
kind: :app,
-
tag_options: { data: { "turbo-method": :post, action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
<%= render LooposUi::Modal.new(title: t(".delete_protocol")) do |modal| %>
-
<% remove = capture do %>
-
<i class="fa-regular fa-trash-alt text-app-800-primary cursor-pointer"></i>
-
<% end %>
-
<% modal.with_trigger do %>
-
<%= react_component("Tooltip", { text: t(".delete_protocol") , children: remove, placement: "top" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm"),
-
href: admin_v2_protocol_path(protocol),
-
kind: :app,
-
tag_options: { data: { "turbo-method": :delete, action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<%= tag.span(t(".delete_protocol_text")) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<div class="protocol-component__trigger" id="#accordion-collapse-<%= dom_id(protocol)%>-header"
-
data-accordion-target="#accordion-collapse-<%= dom_id(protocol)%>"
-
data-action="click->protocol-accordion#toggleClass"
-
aria-controls="accordion-collapse-<%= dom_id(protocol)%>">
-
<svg data-accordion-icon class="w-3 h-3 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
-
then: 0
else: 0
<path xmlns="http://www.w3.org/2000/svg" d="M10.375 1.42188L5.89844 5.71094C5.75781 5.82812 5.61719 5.875 5.5 5.875C5.35938 5.875 5.21875 5.82812 5.10156 5.73438L0.601562 1.42188C0.367188 1.21094 0.367188 0.835938 0.578125 0.625C0.789062 0.390625 1.16406 0.390625 1.375 0.601562L5.5 4.53906L9.60156 0.601562C9.8125 0.390625 10.1875 0.390625 10.3984 0.625C10.6094 0.835938 10.6094 1.21094 10.375 1.42188Z" fill="<%= show_inheritance_path.present? ? "#212529": "#B53C00" %>"/>
-
</svg>
-
</div>
-
</div>
-
else: 0
<% else %>
-
<div class="protocol-component__header-right">
-
then: 0
else: 0
<% if !show_inheritance_path %>
-
then: 0
else: 0
<% if readonly && current_user.can_edit_protocols? %>
-
<i class="fa-regular fa-clone text-gray-400"></i>
-
<i class="fa-regular fa-trash-alt text-gray-400"></i>
-
<% end %>
-
<% end %>
-
<div class="protocol-component__trigger" id="#accordion-collapse-<%= dom_id(protocol)%>-header"
-
data-accordion-target="#accordion-collapse-<%= dom_id(protocol)%>"
-
data-action="click->protocol-accordion#toggleClass"
-
aria-controls="accordion-collapse-<%= dom_id(protocol)%>">
-
<svg data-accordion-icon class="w-3 h-3 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
-
then: 0
else: 0
<path xmlns="http://www.w3.org/2000/svg" d="M10.375 1.42188L5.89844 5.71094C5.75781 5.82812 5.61719 5.875 5.5 5.875C5.35938 5.875 5.21875 5.82812 5.10156 5.73438L0.601562 1.42188C0.367188 1.21094 0.367188 0.835938 0.578125 0.625C0.789062 0.390625 1.16406 0.390625 1.375 0.601562L5.5 4.53906L9.60156 0.601562C9.8125 0.390625 10.1875 0.390625 10.3984 0.625C10.6094 0.835938 10.6094 1.21094 10.375 1.42188Z" fill="<%= show_inheritance_path.present? ? "#212529": "#B53C00" %>"/>
-
</svg>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<turbo-frame id='protocol_sidebar_inner' class='h-full block'>
-
then: 0
else: 0
<div class="protocol-sidebar <%= !@app_block ? "protocol-sidebar--fit" : "h-full"%>">
-
<%# <div class="protocol-sidebar__section protocol-sidebar__section--header copy-16-medium"> %>
-
<%# header %>
-
<%# </div> %>
-
then: 0
else: 0
<div class="protocol-sidebar__content-wrapper <%= !@app_block ? "protocol-sidebar__content-wrapper--full" : ""%>">
-
<% content_sections.each do |section| %>
-
<div class="protocol-sidebar__section">
-
<%= section %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</turbo-frame>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolSidebarComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :header
-
1
renders_many :content_sections
-
-
1
def initialize(app_block: false)
-
@app_block = app_block
-
end
-
end
-
end
-
end
-
<div class="protocol-area flex protocol-sidebar">
-
<div class="protocol-area__section">
-
<%= add_protocol(highlighted: true) %>
-
then: 0
else: 0
<% if inherited_highlighted_protocol_section.present? %>
-
<div class="protocol-area__inherited bg-gray-200" >
-
<div class="protocol-area__inherited-title">
-
<% icon = capture do %>
-
<i class="copy-12 font-bold text-app-800-primary fa-solid fa-lock"></i>
-
<% end %>
-
<%= react_component("Tooltip", { text: "Inherited Protocol" , children: icon, placement: "right" }) %>
-
<p class="copy-12 font-bold ">Inherited protocols</p>
-
</div>
-
<%= inherited_highlighted_protocol_section %>
-
</div>
-
<% end %>
-
<div class="protocol-area__highlighted"
-
data-controller="sortable-protocol"
-
data-sortable-protocol-catalog-node-patch-url-value="<%= @update_path %>">
-
then: 0
else: 0
<% if highlighted_protocol_section.present? %>
-
<%= highlighted_protocol_section%>
-
<% end %>
-
</div>
-
</div>
-
<hr class="protocol-area__separator">
-
<div class="protocol-area__section">
-
<%= add_protocol %>
-
then: 0
else: 0
<% if inherited_protocol_section.present? %>
-
<div class="protocol-area__inherited bg-gray-200" >
-
<div class="protocol-area__inherited-title">
-
<% icon = capture do %>
-
<i class="copy-12 font-bold text-app-800-primary fa-solid fa-lock"></i>
-
<% end %>
-
<%= react_component("Tooltip", { text: "Inherited Protocol" , children: icon, placement: "right" }) %>
-
<p class="copy-12 font-bold ">Inherited protocols</p>
-
</div>
-
<%= inherited_protocol_section%>
-
</div>
-
<% end %>
-
<div class="protocol-area__not-highlighted"
-
data-controller="sortable-protocol"
-
data-sortable-protocol-catalog-node-patch-url-value="<%= @update_path %>">
-
then: 0
else: 0
<% if protocol_section.present? %>
-
<%= protocol_section%>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module Protocol
-
1
class ProtocolsAreaComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :inherited_highlighted_protocol_section
-
1
renders_one :highlighted_protocol_section
-
1
renders_one :inherited_protocol_section
-
1
renders_one :protocol_section
-
-
1
def initialize(update_path: nil, current_user:, turbo_frame_id:, catalog_node:, model:)
-
@update_path = update_path
-
@current_user = current_user
-
@turbo_frame_id = turbo_frame_id
-
@catalog_node = catalog_node
-
@model = model
-
end
-
-
1
def add_protocol(highlighted: false)
-
render(LooposUi::Protocol::AddProtocol.new(
-
highlighted: highlighted,
-
current_user: @current_user,
-
turbo_frame_id: @turbo_frame_id,
-
catalog_node: @catalog_node,
-
model: @model,
-
))
-
end
-
-
1
def render_protocols(protocols, attached_protocols, filter)
-
protocols.map do |protocol|
-
is_readonly = attached_protocols.exclude?(protocol)
-
render(LooposUi::Protocol::ProtocolComponent.new(
-
protocol: protocol,
-
readonly: is_readonly,
-
current_catalog_node: @catalog_node,
-
current_user: @current_user,
-
)) do |component|
-
component.with_elements_list do
-
render(
-
"admin/v2/protocols/protocol_elements_list",
-
protocol: protocol,
-
readonly: is_readonly,
-
current_catalog_node: @catalog_node,
-
show_opened: true,
-
filter: filter,
-
)
-
end
-
end
-
end.join.html_safe
-
end
-
end
-
-
1
class AddProtocol < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
def initialize(highlighted: false, current_user:, turbo_frame_id:, catalog_node:, model:)
-
@highlighted = highlighted
-
@current_user = current_user
-
@turbo_frame_id = turbo_frame_id
-
@catalog_node = catalog_node
-
@model = model
-
end
-
end
-
end
-
end
-
<%= turbo_frame_tag 'protocol_sidebar' do %>
-
<%= turbo_stream_from "protocol_sidebar_stream" %>
-
<%= render LooposUi::Protocol::ProtocolSidebarComponent.new do |sidebar| %>
-
<%
-
=begin%>
-
<% sidebar.with_header do %>
-
<%= turbo_stream_from "protocol_sidebar_stream" %>
-
<div class="flex flex-row items-center justify-between" data-controller="protocol-accordion">
-
<div class="flex flex-row items-center gap-2">
-
<i class="fa-regular fa-bars-staggered"></i>
-
<h1>Protocol Settings</h1>
-
</div>
-
<%= link_to preview_protocol_path, data: { turbo_frame: 'protocol_sidebar', action: "click->protocol-accordion#closeSidebar click->protocol-accordion#selectedProtocol" }, class: 'btn btn-primary' do %>
-
<i class="fa-solid fa-xmark text-gray-600 cursor-pointer"></i>
-
<% end %>
-
</div>
-
<% end %>
-
<%
-
=end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<%= turbo_frame_tag "show_protocol_name", class: "flex flex-row items-center justify-between gap-4" do %>
-
<div class="flex flex-col gap-1">
-
<%= turbo_frame_tag "edit_protocol_name", class: "editComponents flex flex-row items-center gap-base" do %>
-
<p class="text-general-gray-900 copy-14 font-bold"><%= protocol_name %></p>
-
<% end %>
-
<p class="copy-12 text-general-gray-700">
-
<%= t(".protocol_info.update_date") %>: <%= formatted_updated_at %>
-
</p>
-
<p class="copy-12 text-general-gray-700">
-
<%= t(".protocol_info.created_date") %>: <%= formatted_created_at %>
-
</p>
-
</div>
-
<%= link_to edit_protocol_path, class: "min-w-fit", data: { turbo: true, turbo_frame: "edit_protocol_name" } do %>
-
<%= react_component("Button", { text: t(".edit_protocol_name.button"), app: "neutral", icon: "fa-regular fa-pencil", variant: "secondary", size: "medium", iconPosition: "left", dataAttributes: { 'action': "click->modal#open" } }) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<div class="flex flex-row items-center justify-between gap-4">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold"><%= t(".duplicate_protocol.action") %></p>
-
<p class="copy-12 text-general-gray-700"><%= t(".duplicate_protocol.description") %></p>
-
</div>
-
<%= render LooposUi::Modal.for_duplicate(model_name: "Protocol") do |modal| %>
-
<% modal.with_trigger do %>
-
<%= react_component("Button", { text: t(".duplicate_protocol.button"), app: "neutral", icon: "fa-kit fa-regular-copy-circle-plus", variant: "secondary", size: "medium", iconPosition: "left" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm_button"),
-
href: duplicate_protocol_path,
-
kind: :app,
-
tag_options: { data: { "turbo-method": :post, action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<div class="flex flex-row items-center justify-between gap-4">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold"><%= t(".copy_protocol.action") %></p>
-
<p class="copy-12 text-general-gray-700"><%= t(".copy_protocol.description") %></p>
-
</div>
-
<div data-controller="clipboard">
-
<%= react_component("Button", { text: t(".copy_protocol.button"), app: "neutral", icon: "fa-regular fa-copy", variant: "secondary", size: "medium", iconPosition: "left", dataAttributes: { 'action': "click->clipboard#copy", 'clipboard-export-param': copy_protocol_path, 'clipboard-broadcast-param': broadcast_copied_protocol_admin_v2_protocols_path } }) %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<div class="flex flex-row items-center justify-between gap-4">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold"><%= t(".export_protocol.action") %></p>
-
<p class="copy-14 text-general-gray-700"><%= t(".export_protocol.description") %></p>
-
</div>
-
<%= render LooposUi::Modal.for_export(model_name: "Protocol") do |modal| %>
-
<% modal.with_trigger do %>
-
<%= react_component("Button", { text: t(".export_protocol.button"), app: "neutral", icon: "fa-regular fa-download", variant: "secondary", size: "medium", iconPosition: "left" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm_button"),
-
href: export_protocol_path,
-
kind: :app,
-
tag_options: { data: { action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<%
-
=begin%>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<p class="text-general-gray-900 copy-14 font-bold">Tags</p>
-
<div class="flex justify-start mt-6">
-
<%= turbo_frame_tag protocol, 'tags' do %>
-
<%= render 'admin/v2/tags/tags', taggable: protocol %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<%
-
=end %>
-
then: 0
else: 0
<% if catalog_node.present? %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<div class="flex flex-row items-center justify-between gap-4">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold"><%= t(".detach_protocol.action") %></p>
-
<p class="copy-12 text-general-gray-700"><%= t(".detach_protocol.description") %></p>
-
</div>
-
<%= render LooposUi::Modal.for_detach(model_name: "Protocol") do |modal| %>
-
<% modal.with_trigger do %>
-
<%= react_component("Button", { text: t(".detach_protocol.button"), app: "neutral", icon: "fa-kit fa-regular-paperclip-slash", variant: "secondary", size: "medium", iconPosition: "left" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm_button"),
-
href: detach_protocol_path,
-
kind: :app,
-
tag_options: { data: { "turbo-method": :post, action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
<% sidebar.with_content_section do %>
-
<div class="protocol_sidebar__section--padded">
-
<div class="flex flex-row items-center justify-between gap-4">
-
<div class="flex flex-col gap-1">
-
<p class="text-general-gray-900 copy-14 font-bold"><%= t(".delete_protocol.action") %></p>
-
<p class="copy-12 text-general-gray-700"><%= t(".delete_protocol.description") %></p>
-
</div>
-
<%= render LooposUi::Modal.new(title: t(".delete_protocol.action")) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= react_component("Button", { text: t(".delete_protocol.button"), app: "danger", icon: "fa-regular fa-trash-can", variant: "secondary", size: "medium", iconPosition: "left" }) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".confirm_button"),
-
href: delete_protocol_path,
-
kind: :app,
-
tag_options: { data: { "turbo-method": :delete, turbo_frame: "_top", action: "click->lui--button#startLoading click->actions_loading#run" } }
-
) %>
-
<%= tag.span(t(".delete_protocol.modal_text")) %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
class LooposUi::Protocol::Settings < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
-
1
attr_reader :protocol, :presenter, :catalog_node
-
-
1
def initialize(protocol:, presenter:, catalog_node: nil)
-
@protocol = protocol
-
@presenter = presenter
-
@catalog_node = catalog_node
-
end
-
-
1
def protocol_name
-
protocol.name
-
end
-
-
1
def formatted_created_at
-
protocol.created_at.strftime("%d-%m-%Y")
-
end
-
-
1
def formatted_updated_at
-
protocol.updated_at.strftime("%d-%m-%Y")
-
end
-
-
1
def edit_protocol_path
-
presenter.edit_path
-
end
-
-
1
def duplicate_protocol_path
-
then: 0
else: 0
duplicate_admin_v2_protocol_path(protocol, catalog_node_id: catalog_node&.id)
-
end
-
-
1
def delete_protocol_path
-
admin_v2_protocol_path(protocol)
-
end
-
-
1
def preview_protocol_path
-
then: 0
else: 0
preview_admin_v2_protocol_path(protocol, catalog_node_id: catalog_node&.id)
-
end
-
-
1
def export_protocol_path
-
export_admin_v2_protocol_path(protocol, format: :json)
-
end
-
-
1
def copy_protocol_path
-
export_admin_v2_protocol_path(protocol)
-
end
-
-
1
def detach_protocol_path
-
then: 0
else: 0
detach_protocol_admin_v2_catalog_node_path(catalog_node, protocol_id: protocol&.id)
-
end
-
end
-
1
module LooposUi
-
1
class ProtocolAnswerValue < LoopComponent
-
end
-
end
-
Hello from ProtocolAnswerValue component
-
1
module LooposUi
-
1
module ProtocolElement
-
1
class DrawerBar < LoopComponent
-
1
option :element
-
1
option :oauth_core_token
-
1
option :form_authenticity_token
-
1
option :catalog_node
-
1
option :current_user
-
-
1
private
-
-
1
def presenter
-
@presenter ||= element.presenter
-
end
-
end
-
end
-
end
-
<%= render LooposUi::DrawerBar.new(close_on_outside_click: false) do |drawer| %>
-
<% drawer.with_header do %>
-
<%= render LooposUi::DrawerBar::Header.new do |header| %>
-
<% header.with_corner_actions do %>
-
then: 0
else: 0
<% if current_user.can_manage_translations? %>
-
<%= render("shared/translations_for_model", model: element) %> <%# Making changes to leave a conflict here %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::DrawerBar::Entity.new(
-
icon: element.class.icon,
-
title: element.label,
-
description: element.displayed_name
-
)%>
-
<span class="inline-flex">
-
<%= tag.turbo_frame id: dom_id(element, 'tags') do %>
-
<%= render LooposUi::ModelAssociationList.new(
-
model: element,
-
association: :tags,
-
policy: helpers.policy(:catalog_management))
-
%>
-
<% end %>
-
</span>
-
<% end %>
-
<% end %>
-
<% drawer.with_header do %>
-
<%= render LooposUi::DrawerBar::ContentSection.new(
-
title: t(".associate_apps"),
-
description: t(".associate_apps_description")) do %>
-
<%= render "admin/v2/protocol_elements/apps", enabled_apps: presenter.enabled_apps, element: element %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::TabsLayout.new(keep_tab_in_url: false) do |layout| %>
-
<% layout.with_tab(title: t(".settings")) do %>
-
<div class="px-4">
-
<%= react_component("JsonForm", {
-
app: "core",
-
defaultUiSchema: presenter.settings_ui_schema,
-
schema: presenter.settings_schema,
-
settings: element.untranslated_settings || {},
-
resourceId: element.id,
-
resourceClass: element.type,
-
token: oauth_core_token,
-
apiBaseUrl: '/api/v1',
-
fetchSchemaUrl: '/protocol_elements/:resourceId/settings_schema/:scriptId',
-
updateUrl: "/protocol_elements/:id/update_settings?catalog_node_id=#{catalog_node&.id}",
-
then: 0
else: 0
csrfToken: form_authenticity_token,
-
canToggleForm: false,
-
language: I18n.locale,
-
# TODO: Until protocol can support adding or importing scripts...
-
userScopes: (current_user&.scopes&.split(" ") || []) - ["can_manage_scripts"]
-
then: 0
else: 0
then: 0
else: 0
}, class: "font-sans") %>
-
</div>
-
<% end %>
-
<% layout.with_tab(title: "Slug") do %>
-
<div class="flex flex-col gap-4 protocol_sidebar__section px-4">
-
<%= render "admin/v2/protocol_elements/slug_edit", element: element, protocol: element.protocol %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class RadioCard < LoopComponent
-
1
option :value, Types::Coercible::String
-
1
option :name, Types::Coercible::String
-
3
option :checked, Types::Bool, default: -> { false }
-
5
option :disabled, Types::Bool, default: -> { false }
-
1
option :form, Types::Coercible::String, optional: true
-
1
option :data, Types::Hash, optional: true
-
-
1
private
-
-
1
def option_id
-
12
"#{name}-#{value}"
-
end
-
-
1
class Content < LoopComponent
-
1
option :kind, Types::Symbol.enum(:compact, :full), default: -> { :compact }
-
# FIXME: Should only be Money, but review_extra_value sends strings
-
1
option :price, Types::String | Types::Instance(Money), optional: true
-
-
1
renders_one :header, LooposUi::Header
-
-
1
renders_one :money_input
-
1
renders_one :text_area, LooposUi::Inputs::TextArea
-
-
1
renders_one :label_tag, LooposUi::StateLabel
-
-
1
def formated_price
-
6
else: 6
case price
-
when: 0
when String
-
price
-
when: 0
when Money
-
price.format
-
end
-
end
-
end
-
end
-
end
-
6
then: 6
<% if kind == :full %>
-
6
<div class="lui-radio_card__content__full">
-
6
<%= header %>
-
<div class="w-full">
-
6
<%= money_input %>
-
</div>
-
<div class="w-full">
-
6
<%= text_area %>
-
</div>
-
</div>
-
<div class="lui-radio_card__content__compact">
-
6
<%= header %>
-
<div class="lui-radio_card__content__compact__price">
-
6
<%= label_tag %>
-
6
<%= tag.span(formated_price, class: "heading-20 text-content whitespace-nowrap") %>
-
</div>
-
</div>
-
else: 0
<% else %>
-
<div class="lui-radio_card__content__compact-only">
-
<%= header %>
-
<div class="lui-radio_card__content__compact-only__price">
-
<%= label_tag %>
-
<%= tag.span(formated_price, class: "heading-20 text-content whitespace-nowrap") %>
-
</div>
-
</div>
-
<% end %>
-
12
<%= tag.label for: option_id, class: classes, data: data do %>
-
6
<%= tag.input type: "radio", id: option_id, name: name, value: value, checked: checked, disabled: disabled, class: "lui-radio_card__input", form: form %>
-
12
<%= tag.div class: "flex-1 ml-8" do %>
-
6
<%= content %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class ScriptEditor < LoopComponent
-
1
include LooposUi::InlineEditComponent
-
-
1
option :type
-
1
option :can_upload_code, default: -> { false }
-
1
option :loop_os_script, optional: true
-
1
option :download_url, optional: true
-
1
option :attribute
-
3
option :title, default: -> { "" }
-
1
option :turbo_id
-
1
option :default_value
-
3
option :read_only, default: -> { true }
-
1
option :delete_url
-
1
option :upload_url
-
1
option :new_edit_params
-
1
option :upload_validation_file_url, optional: true
-
1
option :delete_validation_file_url, optional: true
-
# new_edit_params can have
-
# [:element, :form_url, :submitable, :show_path, :edit_path, :show_edit_button]
-
end
-
end
-
2
<%= render "loopos_ui/inline/script_editor",
-
type: type,
-
action_buttons: can_upload_code,
-
loop_os_script: loop_os_script,
-
download_url: download_url,
-
attribute: attribute,
-
title: title,
-
turbo_id: turbo_id,
-
default_value: default_value,
-
read_only: read_only,
-
upload_url: upload_url,
-
delete_url: delete_url,
-
upload_validation_file_url: upload_validation_file_url,
-
delete_validation_file_url: delete_validation_file_url,
-
view_component: self,
-
**new_edit_params
-
%>
-
1
module LooposUi
-
1
module Services
-
1
module BaseConcern
-
1
extend ActiveSupport::Concern
-
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
]
-
-
1
included do
-
2
option :data, type: ->(m) { LooposUi::Resources::InvoiceResource.new(model: m) }
-
2
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
2
option :partials_sub_path
-
2
option :tabs_to_render,
-
default: -> {
-
[
-
LooposUi::Services::Invoices::Tabs::Info,
-
{ "extra_data"=> "Extra Data" },
-
{ "preview"=> "Preview" },
-
]
-
},
-
optional: true
-
end
-
-
1
def initialize(...)
-
super
-
@model = data.model
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
2
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
module Services
-
1
module EmailMessages
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
:can_view_templates,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::EmailMessageResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
def before_render
-
@columns = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions)
-
end
-
-
1
private
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".email_id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:status] = { title: t(".state"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:item] = { title: t(".item_id"), dataIndex: "item", key: "item", sortable: true }
-
then: 0
else: 0
table_columns[:partnable] =
-
{
-
title: t(".partner"),
-
dataIndex: "partnable",
-
key: "partnable",
-
sortable: true,
-
} if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{
-
title: t(".service_provider"),
-
dataIndex: "provider",
-
key: "provider",
-
sortable: true,
-
} if can_view_providers?
-
then: 0
else: 0
table_columns[:template] =
-
{
-
title: t(".template"),
-
dataIndex: "template",
-
key: "template",
-
sortable: true,
-
} if can_view_templates?
-
table_columns[:to] = { title: t(".to"), dataIndex: "to", key: "to", sortable: true }
-
then: 0
else: 0
table_columns[:categories] =
-
{
-
title: t(".categories"),
-
dataIndex: "categories",
-
key: "categories",
-
sortable: false,
-
} if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
3
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
!LooposUi.config.app_type?(:manager)
-
end
-
-
1
def services_status(status)
-
case status
-
when: 0
when "created", "pending"
-
:informative
-
when: 0
when "failed"
-
:danger
-
when: 0
when "sent"
-
:success
-
else: 0
else
-
:neutral
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: @columns, **@kwargs) do |table| %>
-
<% @data.each do |data_object| %>
-
<%= service = data_object.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.status.titleize, color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
<% row.with_cell(property: :item) do %>
-
then: 0
<% if service.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: service.item, url: service.item.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
then: 0
<% if service.partnable.present? %>
-
<%= render LooposUi::EntityToken.new(text: service.partnable.name, url: service.partnable.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_partner?%>
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::EntityToken.new(text: service.provider.name, url: service.provider.show_url) %>
-
<% end %>
-
<% row.with_cell(property: :template) do %>
-
<%= render LooposUi::EntityToken.new(text: service.template.name, url: service.template.show_url) %>
-
<% end %>
-
<% row.with_cell(property: :to) do %>
-
<%= tag.div(service.to || "-", class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_email_message"),
-
href: service.show_url,
-
tag_options: { data: { turbo: false } })%>
-
<% end if service.show_url.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module EmailMessages
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::EmailMessageResource.new(model: m) }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<% email = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new( title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".email_details.title"),size: "small", underline: true) do %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".email_details.provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if email.provider.name.present? %>
-
<%= render LooposUi::Token.new(text: email.provider.name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
<%= render LooposUi::FormEntry.new(label: t(".email_details.to"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "to", value: email.to, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".email_details.template"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::EntityToken.new(text: email.template.name, url: email.template.show_url) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".email_details.created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: email.created_at) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".email_details.updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: email.updated_at) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".item_info.title"),size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label:"Item ID", orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if email.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: email.item, url: email.item.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "item_full_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.categories"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if email.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% email.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.product"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if email.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: email.product, url: email.product.show_url )) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.brands"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if email.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% email.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module EmailTemplates
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::EmailTemplateResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
option :float_bar_actions_params, default: -> {
-
{
-
duplicate: { url: nil },
-
export_csv: { url: nil },
-
delete: { url: nil },
-
}
-
}
-
-
1
def before_render
-
@columns = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params)
-
end
-
-
1
private
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:name] = { title: t(".name"), dataIndex: "name", key: "name", sortable: true }
-
table_columns[:subject] = { title: t(".subject"), dataIndex: "subject", key: "subject", sortable: false }
-
table_columns[:template_type] = { title: t(".template_type"), dataIndex: "template_type", key: "template_type", sortable: true }
-
then: 0
else: 0
table_columns[:external_template_id] =
-
{
-
title: t(".external_template_id"),
-
dataIndex: "external_template_id",
-
key: "external_template_id",
-
sortable: true,
-
} if can_view_providers?
-
then: 0
else: 0
table_columns[:partnable] =
-
{
-
title: t(".partner"),
-
dataIndex: "partnable",
-
key: "partnable",
-
sortable: true,
-
sort_key: :partnable_name,
-
} if show_partner?
-
table_columns[:updated_at] =
-
{
-
title: t(".updated_at"),
-
dataIndex: "updated_at",
-
key: "updated_at",
-
sortable: true,
-
}
-
table_columns[:created_at] =
-
{
-
title: t(".created_at"),
-
dataIndex: "created_at",
-
key: "created_at",
-
sortable: true,
-
default_sort: :desc,
-
}
-
-
[:template_type, :external_template_id, :partnable, :updated_at, :created_at].each do |column_key|
-
then: 0
else: 0
then: 0
else: 0
if table_columns[column_key] && columns_extra&.dig(column_key, :filter_key).present?
-
table_columns[column_key][:filters] = columns_extra.dig(column_key, :filters) || []
-
end
-
end
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
#[LooposUi::Resources::EmailTemplateResource::EmailTemplate.new] :template is missing in Hash input
-
true
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: @columns, **@kwargs) do |table| %>
-
<% @data.each do |data_object| %>
-
<% service = data_object.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :name) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.name, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.name, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :subject) do %>
-
<%= tag.div(service.subject, class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% row.with_cell(property: :template_type) do %>
-
then: 0
else: 0
<%= render LooposUi::EntityToken.new(text: service.template_type&.titleize) %>
-
<% end %>
-
then: 0
else: 0
<% row.with_cell(property: :external_template_id) do %>
-
<%= tag.div(service.external_template_id || "-", class: "copy-14 text-general-global-black" )%>
-
<% end if can_view_providers? %>
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
then: 0
<% if service.partnable.present? %>
-
<%= render LooposUi::EntityToken.new(text: service.partnable.name, leading_icon: "fa-regular fa-handshake", url: service.partnable.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_partner? %>
-
<% row.with_cell(property: :updated_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.updated_at) %>
-
<% end %>
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
then: 0
else: 0
<% if service.try(:duplicate_url).present? %>
-
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "fa-kit fa-regular-copy-circle-plus",
-
tooltip_text: t(".duplicate_email_template"),
-
href: service.duplicate_url,
-
tag_options: { data: { method: :post, turbo: false } }) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if service.try(:delete_url).present? %>
-
<% row.with_action do %>
-
<%= render LooposUi::Modal.new(title: t("admin.loop_os_services.email_templates.delete_modal.title"), description: t("admin.loop_os_services.email_templates.delete_modal.description")) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
leading_icon: :trash,
-
type: :tertiary,
-
size: :tiny,
-
kind: :neutral,
-
tooltip_text: t(".delete_email_template")
-
) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t("admin.loop_os_services.email_templates.delete_modal.confirm_button"),
-
tag_options: { type: "submit", "data-turbo-method": :delete },
-
href: service.delete_url,
-
type: :primary
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_email_template"),
-
href: service.show_url,
-
tag_options: { data: { turbo: false } })%>
-
<% end if service.show_url.present? %>
-
<% end %>
-
<% end %>
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:duplicate, :url).present? %>
-
<% float_bar.with_action_duplicate(href: float_bar_actions_params.dig(:duplicate, :url), text: t(".duplicate"), type: :secondary, tooltip_text: t(".duplicate_email_templates")) %>
-
<% end %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:delete, :url).present? %>
-
<% float_bar.with_modal_action(
-
title: t("admin.loop_os_services.email_templates.delete_modal.title"),
-
description: t("admin.loop_os_services.email_templates.delete_modal.description")
-
) do |modal| %>
-
<% modal.with_trigger_button(
-
text: t(".delete"),
-
leading_icon: :trash,
-
size: :tiny,
-
type: :tertiary,
-
kind: :neutral,
-
tooltip_text: t(".delete_email_templates")
-
) %>
-
<% modal.with_primary_action(
-
text: t("admin.loop_os_services.email_templates.delete_modal.confirm_button"),
-
tag_options: { data: { turbo_method: :delete } },
-
href: float_bar_actions_params.dig(:delete, :url),
-
type: :primary
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module EmailTemplates
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_edit,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::EmailTemplateResource.new(model: m) }
-
1
option :form_data, default: -> { {} }
-
-
1
option :available_partnables, default: -> { [] }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def partnables_list
-
(available_partnables.presence || [data.model.partnable]).compact.map { |partnable| { value: partnable.full_id, text: partnable.name } }
-
end
-
end
-
end
-
end
-
end
-
end
-
<%
-
email_template = data.model
-
then: 0
else: 0
form_url = form_data&.dig(:url)
-
then: 0
else: 0
turbo_frame = form_data&.dig(:turbo_frame)
-
-
form_attributes = {
-
url: form_url.presence,
-
method: :put,
-
data: { turbo: true, turbo_frame: turbo_frame },
-
}
-
readonly = form_url.blank? || !can_edit?
-
%>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column(half: true) do %>
-
<%= form_with(**form_attributes) do |f| %>
-
<%= render LooposUi::FormEntry.new(label: t(".id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "id", value: email_template.id, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= form_with(**form_attributes) do |f| %>
-
<%= render LooposUi::FormEntry.new(label: t(".kind"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: "template_type",
-
options: LooposUi::Resources::EmailTemplateResource::TEMPLATE_TYPES.map { |type| { value: type, text: type.titleize } },
-
value: email_template.template_type,
-
placeholder: "-",
-
readonly: readonly,
-
extra_input_attributes: {
-
id: "template_type_input",
-
},
-
clearable: false,
-
)
-
%>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<div class="<%= email_template.template_type == "send_in_blue" ? "" : "hidden" %>">
-
<%= form_with(**form_attributes) do |f| %>
-
<%= render LooposUi::FormEntry.new(label: t(".external_template_id"), orientation: "horizontal", label_width: 140, required: true) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "external_template_id", value: email_template.external_template_id, placeholder: "-", readonly: readonly) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
<%= form_with(**form_attributes) do |f| %>
-
<%= render LooposUi::FormEntry.new(label: t(".subject"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "subject", value: email_template.subject, placeholder: "-", readonly: readonly) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= form_with(**form_attributes) do |f| %>
-
<%= render LooposUi::FormEntry.new(label: t(".partnable"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Select2.new(
-
name: "partnable",
-
options: partnables_list,
-
then: 0
else: 0
value: email_template.partnable&.full_id,
-
placeholder: "-",
-
readonly: readonly,
-
extra_input_attributes: {
-
id: "partnable_input",
-
},
-
clearable: false,
-
)
-
%>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module Services
-
1
class ExtraData < LoopComponent
-
1
option :data, type: ->(m) { LooposUi::Resource.new(model: m) }
-
end
-
end
-
end
-
<%
-
columns = [
-
{ title: 'Key', dataIndex: 'key', key: :key },
-
{ title: 'Value', dataIndex: 'value', key: :value }
-
]
-
-
then: 0
else: 0
service_extra_data = data.model.extra_data&.each_with_object({}) do |(key, value), hash|
-
hash[key] = value
-
end || {}
-
%>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: "Extra Data", size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::ExtraDataViewer.new(
-
data: service_extra_data,
-
readonly: true
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module IncomingPayments
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::IncomingPaymentResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
option :float_bar_actions_params, default: -> {
-
{
-
export_csv: { url: nil },
-
}
-
} # Format: { action_key: { url: string } }
-
-
1
def before_render
-
@columns = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params)
-
end
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] =
-
{ title: t(".incoming_payment_id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:status] = { title: t(".state"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:item] = { title: t(".item_id"), dataIndex: "item", key: "item", sortable: true }
-
table_columns[:amount] = { title: t(".amount"), dataIndex: "amount", key: "amount", sortable: false }
-
then: 0
else: 0
table_columns[:partnable] =
-
{ title: t(".partnable"), dataIndex: "partnable", key: "partnable", sortable: true } if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{ title: t(".provider"), dataIndex: "provider", key: "provider", sortable: true } if can_view_providers?
-
then: 0
else: 0
table_columns[:categories] =
-
{ title: t(".categories"), dataIndex: "categories", key: "categories", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: true } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
2
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
!LooposUi.config.app_type?(:manager)
-
end
-
-
1
def services_status(status)
-
LooposUi::Resources::IncomingPaymentResource::STATUS_LABEL_MAPPING[status.to_sym]
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: @columns, **@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
<% row.with_cell(property: :id) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :reference) do %>
-
then: 0
<% if service.reference.present? %>
-
<%= tag.div(service.reference, class: "copy-14 text-general-global-black") %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.status.titleize, color: services_status(service.status)) %>
-
then: 0
else: 0
<% if can_view_errors? && service.error_messages.present? %>
-
<%= render LooposUi::Tooltip.new(title: "Issues", position: :bottom) do |tooltip| %>
-
<% service.error_messages.each do |error_message| %>
-
<%= tag.div(error_message) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</span>
-
<% end %>
-
-
<% row.with_cell(property: :item) do %>
-
then: 0
<% if service.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: service.item, url: service.item.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :amount) do %>
-
<%= tag.div(service.amount, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: service.partnable, url: service.partnable.show_url) %>
-
<% end if show_partner? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::Entities::Provider.new(provider: service.provider, url: service.provider.show_url) %>
-
<% end if can_view_providers? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<%# Make sure to include :catalog_node %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_incoming_payment"),
-
href: service.show_url,
-
tag_options: { data: { turbo: false } })%>
-
<% end if service.show_url.present? %>
-
-
<% end %>
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:export_csv, :url).present? %>
-
<% float_bar.with_action_export(text: t(".export_csv"), href: float_bar_actions_params.dig(:export_csv, :url)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module IncomingPayments
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::IncomingPaymentResource.new(model: m) }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<% incoming_payment = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new( title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".incoming_payment_details.title"),size: "small", underline: true) do %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".incoming_payment_details.provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if incoming_payment.provider.name.present? %>
-
<%= render LooposUi::Token.new(text: incoming_payment.provider.name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".incoming_payment_details.amount"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "amount", value: incoming_payment.amount, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".incoming_payment_details.paid_date"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: incoming_payment.paid_date) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".incoming_payment_details.created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: incoming_payment.created_at) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".incoming_payment_details.updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: incoming_payment.updated_at) %>
-
<% end %>
-
<% end %>
-
-
<% end %>
-
<% end %>
-
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".item_info.title"),size: "small", underline: true) do %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.item_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if incoming_payment.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: incoming_payment.item, url: incoming_payment.item.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "item_full_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.categories"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if incoming_payment.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% incoming_payment.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.product"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if incoming_payment.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: incoming_payment.product, url: incoming_payment.product.show_url )) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.brands"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if incoming_payment.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% incoming_payment.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Invoices
-
1
class Show < LoopComponent
-
1
include LooposUi::Services::BaseConcern
-
-
1
option :index_services_path
-
end
-
end
-
end
-
end
-
<%= render LooposUi::ShowLayout.new do |layout| %>
-
<% layout.with_action_bar do |ab| %>
-
<% ab.with_breadcrumbs_list do |breadcrumbs| %>
-
<% breadcrumbs.with_breadcrumb(href: "#" ) { "Services" } %>
-
<% breadcrumbs.with_breadcrumb(href: index_services_path ) { "Invoices" } %>
-
then: 0
else: 0
<% breadcrumbs.with_breadcrumb(href: "#" ) { @model.item&.full_id } %>
-
<% end %>
-
<% ab.with_action_buttons do |buttons| %>
-
<% buttons.with_button_group do |g| %>
-
<% g.with_button_back(href: index_services_path, type: :tertiary) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% layout.with_header(title: @model.item&.full_id) do |header| %>
-
<% header.with_title_label_state(**data.status_label_args) %>
-
<% header.with_right_side do %>
-
then: 0
else: 0
then: 0
else: 0
<% if can_view_errors? && @model.error_messages&.any? %>
-
<%= render LooposUi::Services::IssuesArea.new(issues: issues) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::Services::TabsLayout.new(
-
data: @model,
-
permissions: permissions,
-
tabs_to_render: tabs_to_render,
-
partials_sub_path: partials_sub_path
-
) %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Invoices
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::InvoiceResource.new(model: m) }]
-
# TODO: Set type for the columns
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
1
option :float_bar_actions_params, default: -> {
-
{
-
export_csv: { url: nil },
-
}
-
} # Format: { action_key: { url: string } }
-
-
1
def before_render(**kwargs)
-
@kwargs[:columns] = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params)
-
end
-
-
1
private
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".invoice_id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:status] = { title: t(".state"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:item] = { title: t(".item_id"), dataIndex: "item", key: "item", sortable: true }
-
table_columns[:invoice_kind] = { title: t(".invoice_kind"), dataIndex: "kind", key: "kind", sortable: true }
-
table_columns[:amount] = { title: t(".amount"), dataIndex: "amount", key: "amount", sortable: true }
-
table_columns[:vat] = { title: t(".vat"), dataIndex: "vat", key: "vat", sortable: true }
-
then: 0
else: 0
table_columns[:partnable] =
-
{ title: t(".partnable"), dataIndex: "partnable", key: "partnable", sortable: true } if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{ title: t(".provider"), dataIndex: "provider", key: "provider", sortable: true } if can_view_providers?
-
then: 0
else: 0
table_columns[:categories] =
-
{ title: t(".categories"), dataIndex: "categories", key: "categories", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: true } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
2
define_method(:"#{permission_name}?") do
-
permissions.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
!LooposUi.config.app_type?(:manager)
-
end
-
-
1
def services_status(status)
-
case status
-
when: 0
when "created", "pending"
-
:informative
-
when: 0
when "failed"
-
:danger
-
when: 0
when "finalized", "sent"
-
:success
-
else: 0
else
-
:neutral
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(**@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.status.titleize, color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
-
<% row.with_cell(property: :item) do %>
-
then: 0
<% if service.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: service.item, url: service.item.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :kind) do %>
-
<%= render LooposUi::EntityToken.new(text: service.kind.titleize) %>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<%# Make sure to include :catalog_node %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
<% row.with_cell(property: :amount) do %>
-
<%= tag.div(service.amount || "-", class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
-
<% row.with_cell(property: :vat) do %>
-
<%= tag.div(service.vat || "-", class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
then: 0
<% if service.partnable.present? %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: service.partnable, url: service.partnable.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_partner? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::Entities::Provider.new(provider: service.provider, url: service.provider.show_url) %>
-
<% end if can_view_providers? %>
-
-
<% row.with_cell(property: :amount) do %>
-
<%= tag.div(service.amount || "-", class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
-
<%# Not needed for now %>
-
<% row.with_cell(property: :updated_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.updated_at) %>
-
<% end if false %>
-
-
<% row.with_cell(property: :description) do %>
-
<%= tag.div(class: "copy-14 text-general-global-black text-ellipsis whitespace-nowrap overflow-hidden max-w-[250px]") do %>
-
<%= service.description || "-" %>
-
<%= render LooposUi::Tooltip.new(title: "", position: :bottom) do |tooltip| %>
-
<%= service.description %>
-
<% end %>
-
<% end %>
-
<% end if false %>
-
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_invoice"),
-
href: service.show_url,
-
tag_options: { data: { turbo: false } })%>
-
</span>
-
<% end if service.show_url.present? %>
-
<% end %>
-
<% end %>
-
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:export_csv, :url).present? %>
-
<% float_bar.with_action_export(text: "Export CSV", href: float_bar_actions_params.dig(:export_csv, :url)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Invoices
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
-
1
option :data, type: ->(m) { LooposUi::Resources::InvoiceResource.new(model: m) }
-
1
option :icon, default: -> { self.class.icon }
-
1
option :tab_name, default: -> { self.class.tab_name }
-
1
option :tab_title, default: -> { self.class.tab_title }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
def initialize(...)
-
super
-
@model = data.model
-
end
-
-
1
class << self
-
1
def icon
-
"fa-regular fa-memo-circle-info"
-
end
-
-
1
def tab_name
-
"info"
-
end
-
-
1
def tab_title
-
"Invoice Info"
-
end
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: t(".title"), size: "normal" ) %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".invoice_details.title"), underline: "yes", size: "small") do %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if @model.provider.name.present? %>
-
<%= render LooposUi::EntityToken.new(text: @model.provider.name, url: @model.provider.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.kind"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Token.new(text: @model.kind) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.description"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "description", value: @model.description, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.amount"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "amount", value: @model.amount, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.vat"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "vat", value: @model.vat, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: @model.created_at) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".invoice_details.updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: @model.updated_at) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".item_info.title"), underline: "yes", size: "small") do %>
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.item_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if @model.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: @model.item, url: @model.item.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "item_full_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if show_catalog? %>
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.categories"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if @model.categories.any? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% @model.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.product"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if @model.product.present? %>
-
<%= render LooposUi::Entities::Product.new(product: @model.product, url: @model.product.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.brands"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if @model.brands.any? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% @model.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
class IssuesArea < LoopComponent
-
1
option :issues
-
end
-
end
-
end
-
<div class="form-layout__error-details">
-
<h1 class="form-layout__error-details-title">
-
<i class="fa-regular fa-bell-exclamation"></i>
-
Issues
-
</h1>
-
<% issues.each do |issue| %>
-
<p class="form-layout__error-details-value form-layout__error-details-value--margin-b">
-
<%= issue %>
-
</p>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module Services
-
1
class List < LoopComponent
-
1
ORDER = [:emails, :incoming_payments, :invoices, :payments, :sms, :shippings]
-
-
ICONS = {
-
1
emails: "fa-regular fa-envelope",
-
incoming_payments: "fa-regular fa-envelope-open-dollar",
-
invoices: "fa-regular fa-file-invoice",
-
payments: "fa-regular fa-credit-card",
-
sms: "fa-regular fa-message-sms",
-
shippings: "fa-regular fa-truck",
-
}
-
-
1
option :list
-
end
-
end
-
end
-
<%= render LooposUi::TokenList.new do |token_list| %>
-
<% ORDER.each do |service| %>
-
<% details = list[service] %>
-
then: 0
else: 0
<% if details.present? %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::IconTooltip.new(
-
count: details[:count],
-
text: details[:tooltip],
-
icon: ICONS[service]
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Payments
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
:can_approve_services_payments,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::PaymentResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
option :float_bar_actions_params, default: -> {
-
{
-
export_csv: { url: nil },
-
}
-
} # Format: { action_key: { url: string } }
-
-
1
def before_render
-
@columns = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params)
-
end
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".outgoing_payment_id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:reference] = { title: t(".reference"), dataIndex: "reference", key: "reference", sortable: true }
-
table_columns[:status] = { title: t(".state"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:item] = { title: t(".item_id"), dataIndex: "item", key: "item", sortable: true }
-
table_columns[:amount] =
-
{ title: t(".amount"), dataIndex: "amount", key: "amount", sortable: false }
-
then: 0
else: 0
table_columns[:partnable] =
-
{
-
title: t(".outgoing_payment_id"),
-
dataIndex: "partnable",
-
key: "partnable",
-
sortable: true,
-
} if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{
-
title: t(".provider"),
-
dataIndex: "provider",
-
key: "provider",
-
sortable: true,
-
} if can_view_providers?
-
then: 0
else: 0
table_columns[:categories] =
-
{
-
title: t(".categories"),
-
dataIndex: "categories",
-
key: "categories",
-
sortable: false,
-
} if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: true } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
3
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
!LooposUi.config.app_type?(:manager)
-
end
-
-
1
def services_status(status)
-
LooposUi::Resources::PaymentResource::STATUS_LABEL_MAPPING[status.to_sym]
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: @columns, **@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :reference) do %>
-
then: 0
<% if service.reference.present? %>
-
<%= tag.div(service.reference, class: "copy-14 text-general-global-black") %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.status.titleize, color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
-
<% row.with_cell(property: :item) do %>
-
then: 0
<% if service.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: service.item, url: service.item.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :amount) do %>
-
<%= tag.div(service.amount, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: service.partnable, url: service.partnable.show_url) %>
-
<% end if show_partner? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::Entities::Provider.new(provider: service.provider, url: service.provider.show_url) %>
-
<% end if can_view_providers?%>
-
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<%# Make sure to include :catalog_node %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_payment"),
-
href: service.show_url,
-
tag_options: { data: { turbo: false } })%>
-
</span>
-
<% end if service.show_url.present? %>
-
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Modal.new(
-
title: t(".pay_retry_modal_title"),
-
description: t(".confirm_payment"),
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.for_row_action(
-
tooltip_text: t(".pay_retry"),
-
icon: "credit-card"
-
) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".pay"),
-
href: service.payment_retry_url,
-
kind: :app,
-
tag_options: { data: { "mass-assign-target": "action", action: "click->lui--button#startLoading click->actions_loading#run" } },
-
)%>
-
<% end %>
-
<% end if service.payment_retry_url.present? && can_approve_services_payments?%>
-
<% end %>
-
<% end %>
-
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:export_csv, :url).present? %>
-
<% float_bar.with_action_export(text: t(".export_csv"), href: float_bar_actions_params.dig(:export_csv, :url)) %>
-
<% end %>
-
then: 0
else: 0
<% if can_approve_services_payments? && float_bar_actions_params.dig(:payment_retry, :url).present? %>
-
<% float_bar.with_action_manual do %>
-
<%= render LooposUi::Modal.new(
-
title: t(".pay_retry_modal_title"),
-
description: t(".confirm_payment"),
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
text: t(".pay_retry"),
-
type: :secondary,
-
kind: :app,
-
size: :tiny,
-
icon: "credit-card"
-
) %>
-
<% end %>
-
<% modal.with_primary_action(
-
text: t(".pay"),
-
href: float_bar_actions_params.dig(:payment_retry, :url),
-
kind: :app,
-
tag_options: { data: { "float-bar-target": "actionLink", action: "click->lui--button#startLoading click->actions_loading#run" } },
-
)%>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Payments
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::PaymentResource.new(model: m) }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<% payment = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new( title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".outgoing_payment_details.title"),size: "small", underline: true) do %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if payment.provider.name.present? %>
-
<%= render LooposUi::Token.new(text: payment.provider.name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.reference"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "reference", value: payment.reference, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.amount"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "amount", value: payment.amount, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.paid_date"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "paid_date", value: payment.paid_date, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.error_messages"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "error_messages", value: payment.error_messages.join(', '), placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end if payment.error_messages.present? && payment.status.to_s == "failed" %>
-
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: payment.created_at) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".outgoing_payment_details.updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: payment.updated_at) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".item_info.title"),size: "small", underline: true) do %>
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.item_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if payment.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: payment.item, url: payment.item.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "item_full_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.categories"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if payment.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% payment.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.product"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if payment.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: payment.product, url: payment.product.show_url )) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.brands"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if payment.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% payment.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module ShippingPickups
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
]
-
-
1
PICKUP_TYPES = [:automatic, :manual]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::ShippingPickupResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra)
-
end
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:external_id] =
-
{ title: t(".external_id"), dataIndex: "external_id", key: "external_id", sortable: true }
-
table_columns[:status] = { title: t(".status"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:pickup_type] =
-
{ title: t(".type"), dataIndex: "pickup_type", key: "pickup_type", sortable: true }
-
table_columns[:pickup_date] =
-
{ title: t(".pickup_date"), dataIndex: "pickup_date", key: "pickup_date", sortable: true }
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
then: 0
else: 0
then: 0
else: 0
if columns_extra&.dig(:status, :filter_key).present?
-
table_columns[:status][:filters] = LooposUi::Services::Shippings::Table::STATUSES.map do |status|
-
{
-
text: t(".states.#{status}"),
-
value: status,
-
}
-
end
-
end
-
-
then: 0
else: 0
then: 0
else: 0
if columns_extra&.dig(:pickup_type, :filter_key).present?
-
table_columns[:pickup_type][:filters] = PICKUP_TYPES.map do |type|
-
{
-
text: t(".pickup_types.#{type}"),
-
value: type,
-
}
-
end
-
end
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def services_status(status)
-
LooposUi::Resources::ShippingResource::STATUS_LABEL_MAPPING.fetch(status.to_sym, :neutral)
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: table_columns, **@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.external_id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :external_id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.external_id.blank? %>
-
else: 0
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
then: 0
<% elsif service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.external_id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.external_id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.try(:status_label) || t(".states.#{service.status}"), color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
-
<% row.with_cell(property: :pickup_type) do %>
-
<%= tag.div(t(".pickup_types.#{service.pickup_type}"), class: "copy-14 text-general-global-black") %>
-
<% end %>
-
-
<% row.with_cell(property: :pickup_date) do %>
-
<%= render LooposUi::DateShow.new(date: service.pickup_date) %>
-
<% end %>
-
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "fa-regular fa-file-arrow-down",
-
tooltip_text: t(".export_btn"),
-
href: service.export_pdf, tag_options: { data: { turbo: false } }) %>
-
<% end if service.export_pdf.present? %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_btn"),
-
href: service.show_url, tag_options: { data: { turbo: false } }) %>
-
<% end if service.show_url.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module ShippingPickups
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
option :data, type: ->(m) { LooposUi::Resources::ShippingPickupResource.new(model: m) }
-
1
option :shipping_table
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<% shipping_pickup = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column(half: true) do %>
-
<%= render LooposUi::TabsSection.new(title: t(".details_title"),size: "small", underline: true) do %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: shipping_pickup.provider.name, readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".type"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "type", value: t(".types.#{shipping_pickup.pickup_type}"), readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: shipping_pickup.created_at) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".pickup_date"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: shipping_pickup.pickup_date) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".shippings_list_title"), size: "small", underline: true) do %>
-
<%= render shipping_table %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Shippings
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
]
-
-
1
STATUSES = [
-
:created,
-
:pending,
-
:waiting_close,
-
:waiting_receive,
-
:waiting_collection,
-
:received,
-
:collected,
-
:awaiting_update,
-
:sent,
-
:delivered,
-
:delivery_failed,
-
:failed,
-
:canceled,
-
:complex,
-
:collection_failed,
-
:error,
-
:expired,
-
]
-
1
option :data, type: [->(m) { LooposUi::Resources::ShippingResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
1
option :show_shipping_guide, default: -> { false }
-
-
1
option :float_bar_actions_params, default: -> {
-
{
-
export_csv: { url: nil },
-
}
-
} # Format: { action_key: { url: string } }
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params, :show_shipping_guide)
-
end
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".id"), dataIndex: "id", key: "id", sortable: true, default_sort: :desc }
-
table_columns[:reference] =
-
{ title: t(".reference"), dataIndex: "reference", key: "reference", sortable: true }
-
table_columns[:status] = { title: t(".status"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:items] = { title: t(".items"), dataIndex: "items", key: "items" }
-
then: 0
else: 0
table_columns[:partnable] =
-
{ title: t(".partnable"), dataIndex: "partnable", key: "partnable", sortable: true } if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{ title: t(".provider"), dataIndex: "provider", key: "provider", sortable: true } if can_view_providers?
-
table_columns[:tracking_code] =
-
{ title: t(".tracking_code"), dataIndex: "tracking_code", key: "tracking_code", sortable: false }
-
table_columns[:pickup_id] =
-
{ title: t(".pickup_id"), dataIndex: "pickup_id", key: "pickup_id", sortable: false }
-
then: 0
else: 0
table_columns[:categories] =
-
{ title: t(".categories"), dataIndex: "categories", key: "categories", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: true } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:shipping_guide] =
-
{ title: t(".shipping_guide"), dataIndex: "shipping_guide", key: "shipping_guide", sortable: false } if show_shipping_guide
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
then: 0
else: 0
then: 0
else: 0
if columns_extra&.dig(:status, :filter_key).present?
-
table_columns[:status][:filters] = STATUSES.map do |status|
-
{
-
text: t(".states.#{status}"),
-
value: status,
-
}
-
end
-
end
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
2
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
-
1
def services_status(status)
-
LooposUi::Resources::ShippingResource::STATUS_LABEL_MAPPING.fetch(status.to_sym, :neutral)
-
end
-
end
-
end
-
end
-
end
-
-
<%= render LooposUi::V2::Table.new(columns: table_columns, **@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :reference) do %>
-
<%= tag.div(service.reference, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.try(:status_label) || t(".states.#{service.status}"), color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
-
<% row.with_cell(property: :items) do %>
-
then: 0
<% if service.items.present? || service.item.present? %>
-
<% items_list = (service.items.presence || [service.item]).sort_by(&:full_id) %>
-
<%= render LooposUi::TokenList.new(max_tokens: 1) do |token_list| %>
-
<% items_list.each do |item| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Item.new(item: item, url: item.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: service.partnable, url: service.partnable.show_url) %>
-
<% end if show_partner? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::Entities::Provider.new(provider: service.provider, url: service.provider.show_url) %>
-
<% end if can_view_providers? %>
-
-
<% row.with_cell(property: :tracking_code) do %>
-
then: 0
<% if service.tracking_url.present? %>
-
<%= link_to service.tracking_url, data: { turbo: false } do %>
-
<%= tag.div(service.tracking_code, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.tracking_code || '-', class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
<% row.with_cell(property: :pickup_id) do %>
-
then: 0
<% if service.pickup_show_url.present? %>
-
<%= link_to service.pickup_show_url, data: { turbo: false } do %>
-
<%= tag.div(service.pickup_id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.pickup_id || '-', class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
then: 0
<% if service.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<%# Make sure to include :catalog_node %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<% row.with_cell(property: :shipping_guide) do %>
-
then: 0
<% if service.shipping_guide_url.present? %>
-
<%= render LooposUi::Button.new(
-
text: t(".shipping_guide"),
-
leading_icon: "file-pdf",
-
href: service.shipping_guide_url,
-
tag_options: { target: "_blank" },
-
size: :tiny,
-
type: :tertiary,
-
kind: :neutral,
-
load_on_click: false,
-
)
-
%>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end if show_shipping_guide %>
-
-
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_btn"),
-
href: service.show_url, tag_options: { data: { turbo: false } }) %>
-
<% end if service.show_url.present? %>
-
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Modal.new(
-
title: "Update Shipping Status",
-
description: "Are you sure you want to perform this action?",
-
form_id: "update_shipping_status_form_#{service.id}",
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.for_row_action(
-
leading_icon: "arrows-rotate",
-
tooltip_text: t(".update_status_btn")
-
) %>
-
<% end %>
-
<% modal.with_custom_content do %>
-
<div class="modal-container">
-
<div data-controller="mass-assign">
-
<%= form_tag service.update_status_url, method: :get, id: "update_shipping_status_form_#{service.id}", data: { "mass-assign-target": "action" } do %>
-
<div class="modal-form" data-mass-assign-target="modal">
-
<%= hidden_field_tag 'id', service.id %>
-
<div class="modal__subtitle copy-14 modal-container__select-container">
-
Statuses
-
<%= render LooposUi::Inputs::Select2.new(
-
mode: :form,
-
form: "update_shipping_status_form_#{service.id}",
-
bind_hidden_input_to_form: true,
-
name: "status",
-
multiple: false,
-
options: float_bar_actions_params.dig(:update_status, :options).map { |status| { value: status, text: status } },
-
placeholder: "Statuses",
-
) %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<% modal.with_primary_action(
-
text: "Update",
-
kind: :app,
-
tag_options: { data: { action: "click->lui--button#startLoading click->actions_loading#run" } },
-
)%>
-
<% end %>
-
<% end if service.update_status_url.present? %>
-
<% end %>
-
<% end %>
-
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:request_manual_pickup, :url).present? %>
-
<% float_bar.with_action(text: t(".request_manual_pickup_btn"), leading_icon: "fa-regular fa-plus-large", href: float_bar_actions_params.dig(:request_manual_pickup, :url)) %>
-
<% end %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:export_csv, :url).present? %>
-
<% float_bar.with_action_export(text: "Export CSV", href: float_bar_actions_params.dig(:export_csv, :url)) %>
-
<% end %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:update_status, :url).present? %>
-
<% float_bar.with_action_manual do %>
-
<span>
-
<%= render LooposUi::Tooltip.new(title: "Open", position: :top) %>
-
<%= render LooposUi::Modal.new(
-
title: t(".update_status_btn"),
-
description: "Are you sure you want to perform this action?",
-
form_id: "update_shipping_status_form_bulk",
-
) do |modal| %>
-
<% modal.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
leading_icon: "arrows-rotate", text: t(".update_status_btn"),
-
size: :tiny,
-
type: :tertiary, kind: :neutral) %>
-
<% end %>
-
<% modal.with_custom_content do %>
-
<div class="modal-container">
-
<div data-controller="mass-assign">
-
<%= form_tag float_bar_actions_params.dig(:update_status, :url), method: :get, id: "update_shipping_status_form_bulk", data: { "mass-assign-target": "action" } do %>
-
<div class="modal-form" data-mass-assign-target="modal">
-
<div class="modal__subtitle copy-14 modal-container__select-container">
-
Statuses
-
<%= hidden_field_tag :status, value: nil %>
-
<% status_options = float_bar_actions_params.dig(:update_status, :options).map do |status| %>
-
<% {
-
value: status,
-
text: status,
-
attributes: {
-
id: status,
-
"data-action": "click->mass-assign#updateInputValue",
-
"data-value": status,
-
"data-attribute": "status"
-
}
-
} %>
-
<% end %>
-
<%= render LooposUi::Inputs::Select2.new(
-
mode: :form,
-
name: "status",
-
multiple: false,
-
options: status_options,
-
placeholder: "Statuses",
-
) %>
-
<div class="select-form" id="shipping-status-select-form-bulk"></div>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<% modal.with_primary_action(
-
text: "Update",
-
kind: :app,
-
tag_options: { data: { action: "click->lui--button#startLoading click->actions_loading#run" } },
-
)%>
-
<% end %>
-
</span>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Shippings
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::ShippingResource.new(model: m) }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
<% shipping = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".details_title"), size: "small", underline: true) do %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if shipping.provider.name.present? %>
-
<%= render LooposUi::Token.new(text: shipping.provider.name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".service_method_name"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if shipping.service_method_name.present? %>
-
<%= render LooposUi::Token.new(text: shipping.service_method_name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "service_method_name", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".package_count"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
else: 0
<%= render LooposUi::Inputs::Text.new(name: "package_count", value: shipping.packages&.size || 0, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".items_count"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
else: 0
<%= render LooposUi::Inputs::Text.new(name: "items_count", value: shipping.items&.size || 0, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".pickup_number"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if shipping.pickup_id.present? %>
-
<% link_to shipping.pickup_show_url, data: { turbo: false } do %>
-
<%= render LooposUi::Inputs::Text.new(name: "close_pickup_id", value: shipping.pickup_id, readonly: true) %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "close_pickup_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: t(".created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: shipping.created_at) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: shipping.updated_at) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".sender_info_title"), size: "small", underline: true) do %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".sender_name"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "sender_name", value: shipping.sender_info.name, readonly: true) %>
-
<% end %>
-
<% end if shipping.sender_info.name.present? %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sender_street"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "sender_street", value: shipping.sender_info.street, readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sender_zipcode"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "zipcode", value: shipping.sender_info.zipcode, readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sender_city"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "sender_city", value: shipping.sender_info.city, readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".receiver_info_title"),size: "small", underline: true) do %>
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".receiver_name"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "receiver_name", value: shipping.receiver_info.name, readonly: true) %>
-
<% end %>
-
<% end if shipping.receiver_info.name.present? %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".receiver_street"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "receiver_street", value: shipping.receiver_info.street, readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".receiver_zipcode"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "zipcode", value: shipping.receiver_info.zipcode, readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".receiver_city"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "receiver_city", value: shipping.receiver_info.city, readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module Shippings
-
1
module Tabs
-
1
class Packages < LoopComponent
-
1
option :data, type: ->(m) { LooposUi::Resources::ShippingResource.new(model: m) }
-
1
option :turbo_frame_id, optional: true
-
-
1
[:core, :manager, :hubs].each do |app_type|
-
3
define_method(:"#{app_type}?") do
-
LooposUi.config.app_type?(app_type)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
<%
-
columns = [
-
{ title: t(".item"), dataIndex: 'id', key: :id, sortable: false },
-
core? && { title: t(".identifiers"), dataIndex: 'identifiers', key: :state, sortable: false },
-
core? && { title: t(".category"), dataIndex: 'category', key: :state, sortable: false },
-
core? && { title: t(".product"), dataIndex: 'product', key: :state, sortable: false },
-
core? && { title: t(".brand"), dataIndex: 'brand', key: :state, sortable: false },
-
(hubs? || core?) && { title: t(".updated_at"), dataIndex: 'updated_at', key: :state, sortable: false },
-
(hubs? || core?) && { title: t(".created_at"), dataIndex: 'created_at', key: :state, sortable: false },
-
].compact_blank
-
-
packages = data.model.packages
-
%>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new(title: t(".title"), description: t(".subtitle"), size: "normal") %>
-
<% end %>
-
<% end %>
-
<% packages.each_with_index do |package, index| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<% title = capture do %>
-
<%= t(".package", id: package.id) %>
-
<% end %>
-
<%= render LooposUi::Accordion.new(title: title, open: index == 0) do %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: t(".package_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "package_id", value: package.id, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".tracking_code"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<% input = capture do %>
-
<div class="flex flex-row">
-
then: 0
else: 0
<div class="<%= package.tracking_url.present? && package.tracking_code.present? ? "underline" : "" %>">
-
<%= render LooposUi::Inputs::Text.new(name: "tracking_code", value: package.tracking_code, placeholder: "-", readonly: true) %>
-
</div>
-
</div>
-
<% end %>
-
then: 0
<% if package.tracking_url.present? && package.tracking_code.present? %>
-
<%= link_to package.tracking_url, target: "_blank" do %>
-
<%= input %>
-
<% end %>
-
else: 0
<% else %>
-
<%= input %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".weight"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new(name: "weight", value: package.weight, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% row.with_column do %>
-
<%= render LooposUi::FormEntry.new(label: t(".created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: package.created_at) %>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::FormEntry.new(label: t(".updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: package.updated_at) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if package.items.present? %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::V2::Table.new(id: "items_table", data: package.items, columns: columns, show_pagination: false, show_result_count: false, searchable: false) do |table| %>
-
<% package.items.each do |item| %>
-
<% table.with_row(key: item.full_id) do |table_row| %>
-
<% table_row.with_cell(property: :id) do %>
-
then: 0
<% if item.show_url.present? %>
-
<%= link_to item.show_url, data: { "turbo-frame": turbo_frame_id } do %>
-
<%= item.full_id %>
-
<% end %>
-
else: 0
<% else %>
-
<%= item.full_id %>
-
<% end %>
-
<% end %>
-
<% table_row.with_cell(property: :identifiers) do %>
-
<div class="flex gap-2">
-
then: 0
<% if item.identifiers.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% item.identifiers.each do |identifier| %>
-
<% token_list.with_token_manual do %>
-
<% identifier_display = OpenStruct.new(key_text: t("admin.items.identifier_kinds.#{identifier.kind}"), text: identifier.reference) %>
-
<%= render LooposUi::Entities::Identifier.new(identifier: identifier_display) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
</div>
-
<% end %>
-
<% table_row.with_cell(property: :category) do %>
-
then: 0
<% if item.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% item.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url.presence) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
<% end %>
-
<% table_row.with_cell(property: :product) do %>
-
then: 0
<% if item.product.present? %>
-
<%= render LooposUi::Entities::Product.new(product: item.product, url: item.product.show_url.presence) %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
<% end %>
-
<% table_row.with_cell(property: :brand) do %>
-
then: 0
<% if item.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% item.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url.presence) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
-
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% table_row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: item.created_at) %>
-
<% end if item.created_at.present? %>
-
then: 0
else: 0
<% table_row.with_cell(property: :updated_at) do %>
-
<%= render LooposUi::DateShow.new(date: item.updated_at) %>
-
<% end if item.updated_at.present? %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module SmsMessages
-
1
class Table < LooposUi::V2::Table
-
1
include Turbo::FramesHelper
-
1
PERMISSIONS = [
-
:can_view_errors,
-
:can_view_providers,
-
]
-
-
1
option :data, type: [->(m) { LooposUi::Resources::SmsMessageResource.new(model: m) }]
-
-
1
option :columns_extra, default: -> { {} } # Format: { column_key: { hidden: boolean, filters: array } }
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
option :float_bar_actions_params, default: -> {
-
{
-
export_csv: { url: nil },
-
}
-
} # Format: { action_key: { url: string } }
-
-
1
def before_render
-
@columns = table_columns
-
end
-
-
1
def initialize(**kwargs)
-
super(columns: [], **kwargs)
-
@kwargs = kwargs.except(:columns_extra, :permissions, :float_bar_actions_params)
-
end
-
-
1
def table_columns
-
table_columns = HashWithIndifferentAccess.new
-
-
table_columns[:id] = { title: t(".sms_id"), dataIndex: "id", key: "id", sortable: true }
-
table_columns[:status] = { title: t(".state"), dataIndex: "status", key: "status", sortable: true }
-
table_columns[:item] = { title: t(".item_id"), dataIndex: "item", key: "item", sortable: true }
-
then: 0
else: 0
table_columns[:partnable] =
-
{
-
title: t(".partnable"),
-
dataIndex: "partnable",
-
key: "partnable",
-
sortable: true,
-
} if show_partner?
-
then: 0
else: 0
table_columns[:provider] =
-
{
-
title: t(".provider"),
-
dataIndex: "provider",
-
key: "provider",
-
sortable: true,
-
} if can_view_providers?
-
table_columns[:to] = { title: t(".to"), dataIndex: "to", key: "to", sortable: true }
-
then: 0
else: 0
table_columns[:categories] =
-
{ title: t(".categories"), dataIndex: "categories", key: "categories", sortable: false } if show_catalog?
-
then: 0
else: 0
table_columns[:product] =
-
{ title: t(".product"), dataIndex: "product", key: "product", sortable: true } if show_catalog?
-
then: 0
else: 0
table_columns[:brands] =
-
{ title: t(".brands"), dataIndex: "brands", key: "brands", sortable: false } if show_catalog?
-
table_columns[:created_at] =
-
{ title: t(".created_at"), dataIndex: "created_at", key: "created_at", sortable: true }
-
-
table_columns.deep_merge!(columns_extra.slice(*table_columns.keys.map(&:to_sym)))
-
-
table_columns.values
-
end
-
-
1
PERMISSIONS.each do |permission_name|
-
2
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_partner?
-
LooposUi.config.app_type?(:manager)
-
end
-
-
1
def show_catalog?
-
!LooposUi.config.app_type?(:manager)
-
end
-
-
1
def services_status(status)
-
case status
-
when: 0
when "created", "sent"
-
:success
-
when: 0
when "pending"
-
:informative
-
when: 0
when "failed"
-
:danger
-
else: 0
else
-
:neutral
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Table.new(columns: @columns, **@kwargs) do |table| %>
-
<% data.each do |resource| %>
-
<% service = resource.model %>
-
<% table.with_row(key: service.id) do |row| %>
-
then: 0
else: 0
<% row.with_cell(property: :id, **(service.latest_error_message.present? && can_view_errors? ? { error_messages: [service.latest_error_message] } : {})) do %>
-
then: 0
<% if service.show_url.present? %>
-
<%= link_to service.show_url, data: { turbo: false } do %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.div(service.id, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
<% end %>
-
<% row.with_cell(property: :status) do %>
-
<span>
-
<%= render LooposUi::StateLabel.new(text: service.status.titleize, color: services_status(service.status)) %>
-
</span>
-
<% end %>
-
<% row.with_cell(property: :item) do %>
-
then: 0
<% if service.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: service.item, url: service.item.show_url) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% row.with_cell(property: :partnable) do %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: service.partnable, url: service.partnable.show_url) %>
-
<% end if show_partner? %>
-
then: 0
else: 0
<% row.with_cell(property: :provider) do %>
-
<%= render LooposUi::Entities::Provider.new(provider: service.provider, url: service.provider.show_url) %>
-
<% end if can_view_providers? %>
-
<% row.with_cell(property: :to) do %>
-
<%= tag.div(service.to, class: "copy-14 text-general-global-black") %>
-
<% end %>
-
then: 0
else: 0
<% row.with_cell(property: :categories) do %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<%# Make sure to include :catalog_node %>
-
<% service.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<% row.with_cell(property: :product) do %>
-
then: 0
<% if service.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: service.product, url: service.product.show_url )) %>
-
else: 0
<% else %>
-
<%= tag.div('-', class: "copy-14 text-general-global-black" )%>
-
<% end %>
-
<% end if show_catalog? %>
-
then: 0
else: 0
<% row.with_cell(property: :brands) do %>
-
then: 0
<% if service.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 2) do |token_list| %>
-
<% service.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url ) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end if show_catalog? %>
-
<% row.with_cell(property: :created_at) do %>
-
<%= render LooposUi::DateShow.new(date: service.created_at) %>
-
<% end %>
-
<%# Actions %>
-
then: 0
else: 0
<% row.with_action do %>
-
<%= render LooposUi::Button.for_row_action(
-
icon: "arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: t(".open_sms_message"),
-
href: service.show_url, tag_options: { data: { turbo: false } })%>
-
<% end if service.show_url.present? %>
-
<% end %>
-
<% end %>
-
<%= table.with_action_bar do |float_bar| %>
-
then: 0
else: 0
<% if float_bar_actions_params.dig(:export_csv, :url).present? %>
-
<% float_bar.with_action_export(text: t(".export_csv"), href: float_bar_actions_params.dig(:export_csv, :url)) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
module SmsMessages
-
1
module Tabs
-
1
class Info < LoopComponent
-
1
PERMISSIONS = [
-
:can_view_providers,
-
]
-
1
option :data, type: ->(m) { LooposUi::Resources::SmsMessageResource.new(model: m) }
-
-
1
option :permissions, default: -> { PERMISSIONS.to_h { |p| [p, false] } }
-
-
1
PERMISSIONS.each do |permission_name|
-
1
define_method(:"#{permission_name}?") do
-
then: 0
else: 0
@permissions&.dig(permission_name)
-
end
-
end
-
-
1
def show_catalog?
-
LooposUi.config.app_type?(:core)
-
end
-
end
-
end
-
end
-
end
-
end
-
<% sms = data.model %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TitleDescription.new( title: t(".title"), description: t(".description"), size: "normal") %>
-
<% end %>
-
<% end %>
-
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".sms_details.title"),size: "small", underline: true) do %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".sms_details.provider"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if sms.provider.name.present? %>
-
<%= render LooposUi::Token.new(text: sms.provider.name) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new(name: "provider", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if can_view_providers? %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sms_details.to"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::Inputs::Text.new( name: "to", value: sms.to, placeholder: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sms_details.template"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::EntityToken.new(text: sms.template.name, url: sms.template.show_url, data: {turbo: false}) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sms_details.created_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: sms.created_at) %>
-
<% end %>
-
<% end %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".sms_details.updated_at"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
<%= render LooposUi::DateShow.new(date: sms.updated_at) %>
-
<% end %>
-
<% end %>
-
-
<% end %>
-
<% end %>
-
-
<% row.with_column do %>
-
<%= render LooposUi::TabsSection.new(title: t(".item_info.title"),size: "small", underline: true) do %>
-
-
<%= render LooposUi::FormEntry.new(label: t(".item_info.item_id"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if sms.item.present? %>
-
<%= render LooposUi::Entities::Item.new(item: sms.item, url: sms.item.show_url) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "item_full_id", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.categories"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if sms.categories.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% sms.categories.each do |category| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Category.new(category: category, url: category.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "categories", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.product"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if sms.product.present? %>
-
<%= render(LooposUi::Entities::Product.new(product: sms.product, url: sms.product.show_url )) %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "product", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
then: 0
else: 0
<%= render LooposUi::FormEntry.new(label: t(".item_info.brands"), orientation: "horizontal", label_width: 140) do |entry| %>
-
<%= entry.with_input do %>
-
then: 0
<% if sms.brands.present? %>
-
<%= render LooposUi::TokenList.new(max_tokens: 3) do |token_list| %>
-
<% sms.brands.each do |brand| %>
-
<% token_list.with_token_manual do %>
-
<%= render LooposUi::Entities::Brand.new(brand: brand, url: brand.show_url) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::Inputs::Text.new( name: "brands", value: "-", readonly: true) %>
-
<% end %>
-
<% end %>
-
<% end if show_catalog? %>
-
-
<% end %>
-
<% end %>
-
<% end %>
-
-
<% end %>
-
1
module LooposUi
-
1
module Services
-
1
class TabsLayout < LoopComponent
-
1
include LooposUi::Services::BaseConcern
-
end
-
end
-
end
-
<%
-
icons = {
-
"info" => "fa-regular fa-memo-circle-info",
-
"packages" => "fa-regular fa-box-taped",
-
"extra_data" => "fa-regular fa-folders",
-
"preview" => "fa-regular fa-eye"
-
}
-
-
then: 0
else: 0
active_tab = params[:tab]&.parameterize(separator: "_") || tabs_to_render.first
-
%>
-
<%= render LooposUi::TabsLayout.new do |layout| %>
-
<% tabs_to_render.each do |tab_object| %>
-
<%
-
then: 0
else: 0
key = tab_object.is_a?(Hash) ? tab_object.keys.first : tab_object.tab_name
-
then: 0
else: 0
title = tab_object.is_a?(Hash) ? tab_object.values.first : tab_object.tab_title
-
then: 0
else: 0
is_active = active_tab.is_a?(Hash) ? key == active_tab.keys.first : key == active_tab
-
then: 0
else: 0
icon = tab_object.is_a?(Hash) ? icons[key] : tab_object.icon
-
%>
-
then: 0
else: 0
<% next if key == "preview" %>
-
<% layout.with_tab(title: title, icon: icon, active: is_active, key: key) do %>
-
<% case tab_object %>
-
when: 0
<% when Hash %>
-
then: 0
<% if key == "extra_data" %>
-
<%= render LooposUi::TabsContent.new do |tab| %>
-
<% tab.with_row do |row| %>
-
<% row.with_column do %>
-
<div class="tabs">
-
<div class="flex-1">
-
<div class="info-tab__section pt-6">
-
<%= render LooposUi::Services::ExtraData.new(data: @model) %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render "#{partials_sub_path}/#{key}", service: @model %>
-
<% end %>
-
when: 0
<% when String %>
-
<%= render "#{partials_sub_path}/#{key}", service: @model %>
-
else: 0
<% else %>
-
<%= render tab_object.new(data: @model, permissions: permissions) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ShowHeader < LoopComponent
-
1
include Turbo::FramesHelper
-
1
include LooposUi::ResourceAware
-
-
1
renders_one :title_zone
-
-
1
renders_many :tokens # "LooposUi::Token"
-
1
renders_many :title_labels, types: {
-
manual: ->(&block) { capture(&block) },
-
counter: LooposUi::CounterLabel,
-
state: LooposUi::StateLabel,
-
double_state: LooposUi::DoubleStateLabel,
-
}
-
-
1
renders_one :details
-
-
1
attr_reader :title
-
-
# BACKCOMPATIBILITY - may be removed
-
1
renders_one :right_side
-
1
renders_one :bottom_side
-
1
attr_reader :top_page_args
-
-
# New APi
-
-
1
renders_one :image, LooposUi::V2::Image
-
1
renders_one :token_zone
-
-
# Do not use directly, use with_detail_zone
-
1
renders_many :_detail_zones
-
-
# TODO: replace with DRY::initializer
-
1
def initialize(title: nil, model: nil, **kwargs)
-
@title = title
-
@model = model
-
# BACKCOMPATIBILITY - may be removed
-
@kwargs = {
-
top_page_args: {
-
rounded: false,
-
image: false,
-
},
-
}.merge!(kwargs)
-
-
@top_page_args = @kwargs[:top_page_args]
-
@detail_zones_count = 0
-
end
-
-
1
def with_detail_zone(...)
-
@detail_zones_count += 1
-
-
then: 0
else: 0
raise ArgumentError, "You can't have more than 3 detail zones" if @detail_zones_count > 3
-
-
with__detail_zone(...)
-
end
-
-
1
def before_render
-
then: 0
else: 0
if resource.present? && @title.blank?
-
@title = resource.model_title
-
end
-
-
else: 0
if !title_zone? && @title.present?
-
then: 0
-
with_title_zone do
-
tag.h1(@title, class: "lui-show-header__title")
-
end
-
end
-
end
-
end
-
end
-
<turbo-frame id="lui-show-header" class="lui-show-header">
-
<div class="loopui-form-layout__main-info">
-
<div class="loopui-form-layout__details">
-
<div class="loopui-form-layout__details-section">
-
then: 0
else: 0
<% if image? %>
-
<%= image %>
-
<% end %>
-
<div class="loopui-form-layout__info-section">
-
then: 0
else: 0
<% if token_zone? || tokens.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
then: 0
else: 0
<%= token_zone if token_zone? %>
-
<% tokens.each do |token| %>
-
<%= token %>
-
<% end %>
-
</div>
-
<% end %>
-
<div class="loopui-form-layout__middle-wrapper--small">
-
then: 0
else: 0
<% if title_zone? %>
-
<div class="flex">
-
<%= title_zone %>
-
<div class="flex gap-2 ml-2">
-
<% title_labels.each do |label| %>
-
<%= label %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if details.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
<div class="pl-1">
-
<%= details %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if bottom_side.present? %>
-
<div class="loopui-form-layout__middle-wrapper">
-
<div class="loopui-form-layout__line-wrapper">
-
<div class="pl-1">
-
<%= bottom_side %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if _detail_zones.present? %>
-
<div class="loopui-form-layout__middle-wrapper">
-
<div class="loopui-form-layout__line-wrapper">
-
<div class="lui-show-header__detail_zones flex flex-col gap-2">
-
<% _detail_zones.each do |detail| %>
-
<div>
-
<%= detail %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if right_side.present? %>
-
<div class="loopui-form-layout__right">
-
<%= right_side %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
</turbo-frame>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class ShowLayout < LoopComponent
-
1
option :model, optional: true
-
1
renders_one :action_bar, ->(**args, &block) {
-
LooposUi::ActionBar.new(**args, model: model, current_action: :show, &block)
-
}
-
# renders_one :header, ->(**args) {
-
# case header_type
-
# when :show
-
# LooposUi::ShowHeader.new(**args, model: model)
-
# else
-
# LooposUi::PageHeader.new(**args, model: model)
-
# end
-
# }
-
#
-
#
-
-
1
renders_one :header, types: {
-
page: { renders: LooposUi::PageHeader, as: :page_header },
-
show: { renders: LooposUi::ShowHeader, as: :header },
-
}
-
-
1
renders_one :highlight
-
end
-
end
-
<div class="lui-show-layout ">
-
<%= action_bar %>
-
<div class="lui-show-layout__container">
-
<div class="lui-show-layout__content">
-
<%= header %>
-
then: 0
else: 0
<% if highlight? %>
-
<div class="lui-show-layout__highlight">
-
<%= highlight %>
-
</div>
-
<% end %>
-
<main class="lui-show-layout__main" style="height: calc(100% - var(--show-layout-header-height));">
-
<%= content %>
-
</main>
-
<turbo-frame class="lui-show-layout__drawer_wrapper" id="lui-main-layout-drawer_bar" class="fixed top-[106px] right-0" data-controller="drawer-bar">
-
</turbo-frame>
-
</div>
-
</div>
-
</div>
-
<div id="app_layout_frame" class="loopui-form-layout">
-
<%= top_page_actions %>
-
<%= top_page_info %>
-
<%= tabs %>
-
<%= table_section %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar < LoopComponent
-
1
option :config, default: -> { LooposUi.config.sidebar }
-
-
1
USE_UI_2 = false
-
-
1
def call
-
then: 0
if USE_UI_2
-
render(LooposUi::Sidebar::V2::Sidebar.new(config: config))
-
else: 0
else
-
render(LooposUi::Sidebar::V1::Sidebar.new(config: config))
-
end
-
end
-
-
1
class << self
-
1
attr_writer :configuration
-
-
1
def configuration
-
1
@configuration ||= Configuration.new
-
end
-
-
1
def configure
-
1
yield configuration
-
end
-
end
-
-
# TODO: test check visible?
-
# TODO: update documentation, active no longer necessary, explain :exact, :icluded, :exculed
-
1
def items
-
item_config_list.map do |attrs|
-
LooposUi::Sidebar::Item.from_hash(attrs)
-
end
-
end
-
-
1
private
-
-
1
def item_config_list
-
then: 0
else: 0
then: 0
if config.items&.any?
-
config.items
-
else: 0
else # backward compatibility to old initializer
-
(config.top_items || []) + (config.bottom_items || [])
-
end || []
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Sidebar
-
1
class Configuration
-
1
attr_accessor :groups
-
1
alias_method :items, :groups
-
1
alias_method :items=, :groups=
-
-
1
attr_accessor :foo
-
-
1
attr_accessor :logout_path
-
-
# V1 DEPRECATED, but supported
-
1
attr_accessor :top_items, :bottom_items
-
-
1
def initialize
-
1
@top_items ||= []
-
1
@bottom_items ||= []
-
1
@groups ||= []
-
1
@foo ||= {}
-
end
-
end
-
end
-
end
-
<div class="w-[64px] h-px bg-gray-500">
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar
-
1
module V1
-
1
class Divider < LoopComponent
-
end
-
end
-
end
-
end
-
<%= tag.div(class: "lui-sidebar__drawer__divider", role: "separator") %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar
-
1
module V1
-
1
class DrawerDivider < LoopComponent
-
end
-
end
-
end
-
end
-
<%= link_to path, class: "lui-sidebar__drawer__entry #{classes}" , data: { turbo_action: "advance" } do %>
-
<%= title %>
-
<% end %>
-
<div class="w-full"
-
then: 0
else: 0
data-action="<%= !disabled? ? "mouseleave->sidebar#hide mouseenter->sidebar#show" : "" %>"
-
data-id="<%= data_target_modal %>"
-
>
-
<%= link_to path, data: { turbo_action: "advance" } do %>
-
<div id=<%= id %> class="lui-sidebar__item <%= sidebar_item_classes %>"
-
style="<%= sidebar_item_styles %>">
-
then: 0
else: 0
<%= render LooposUi::Tooltip.new(title: tooltip_title, position: :right) if tooltip_title.present? %>
-
then: 0
else: 0
<% if highlight %>
-
<div class="lui-sidebar__highlight">
-
<span class="lui-sidebar__highlight--animation"></span>
-
<span class="lui-sidebar__highlight--circle"></span>
-
</div>
-
<% end %>
-
then: 0
<% if image.present? %>
-
<%= image_tag(image, class: "h-4") %>
-
else: 0
<% else%>
-
<%= content_tag(:icon, "", class: icon_classes) %>
-
<% end %>
-
<div class="lui-sidebar__item__short-title">
-
<%= tag.p(short_title, class: "lui-sidebar__item__label") %>
-
then: 0
else: 0
<%= render LooposUi::Counter.new(count: counter, kind: counter_kind, size: :tinny) if render_counter? %>
-
</div>
-
</div>
-
<% end %>
-
<%# FIXME: data-drawer-taget is also used by flowbite, causing errors %>
-
<div class="hidden lui-sidebar__drawer">
-
<div class="lui-sidebar__drawer__header">
-
<div class="lui-sidebar__drawer__header-title">
-
<div>
-
then: 0
<% if image.present? %>
-
<%= image_tag(image, class: "h-4") %>
-
else: 0
<% else%>
-
<%= content_tag(:icon, "", class: "lui-sidebar__drawer__icon #{icon_classes}") %>
-
<% end %>
-
</div>
-
<div>
-
<%= title %>
-
</div>
-
</div>
-
<div class="lui-sidebar__drawer__header-description">
-
<%= description %>
-
</div>
-
</div>
-
<div class="lui-sidebar__drawer__items">
-
<% menu_items_config.each do |menu_entry| %>
-
<%= render LooposUi::Sidebar::V1::DrawerItem.menu_entry_from(menu_entry) %>
-
<% end %>
-
</div>
-
</div>
-
</div>
-
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar
-
1
module V1
-
1
class DrawerItem < Item
-
1
include ActiveLinkTo
-
-
1
attr_reader :description
-
-
1
class << self
-
1
def drawer_menu_divider?(attrs)
-
attrs == :divider || (attrs.is_a?(Hash) && attrs[:type].to_s == "divider")
-
end
-
-
1
def menu_entry_from(attrs)
-
case attrs
-
when: 0
when :divider
-
DrawerDivider.new
-
when: 0
when Hash
-
then: 0
else: 0
drawer_menu_divider?(attrs) ? DrawerDivider.new : DrawerEntry.new(attrs: attrs)
-
else: 0
else
-
raise ArgumentError, "Invalid drawer menu item: #{attrs.inspect}"
-
end
-
end
-
end
-
-
1
def initialize(attrs:)
-
else: 0
then: 0
raise ArgumentError, "DrawerItem requires a :menu_items attribute" unless attrs[:menu_items].present?
-
-
super(attrs: attrs)
-
-
@path = attrs[:path]
-
@description = attrs[:description]
-
end
-
-
1
def path
-
then: 0
@_path ||= if @path.nil? || @disabled
-
"#"
-
else: 0
# TODO: document this, after ui-34 merged
-
then: 0
elsif @path == :first_item
-
then: 0
else: 0
first_item_path = first_link_menu_item&.dig(:path)
-
then: 0
else: 0
first_item_path.respond_to?(:call) ? instance_exec(&first_item_path) : first_item_path
-
else: 0
else
-
then: 0
else: 0
@path.respond_to?(:call) ? instance_exec(&@path) : @path
-
end
-
end
-
-
1
def active?
-
# FIXME: document active states / possible values. We should always forward @active, but i think this
-
# may break some existing code. is_active_link? supports boolean
-
then: 0
else: 0
active_value = @active.respond_to?(:call) ? instance_exec(&@active) : @active
-
then: 0
else: 0
return active_value if active_value == true || active_value == false
-
-
is_active_link?(path, :exclusive) || submenu_item_active?
-
end
-
-
1
def submenu_item_active?
-
link_menu_paths.any? do |path|
-
then: 0
else: 0
path = path.respond_to?(:call) ? instance_exec(&path) : path
-
-
is_active_link?(path || "#", :inclusive)
-
end
-
end
-
-
1
def menu_items_config
-
@data[:menu_items]
-
end
-
-
1
def link_menu_paths
-
menu_items_config.filter_map do |entry|
-
then: 0
else: 0
next if self.class.drawer_menu_divider?(entry)
-
else: 0
then: 0
next unless entry.is_a?(Hash)
-
-
entry[:path]
-
end
-
end
-
-
1
def first_link_menu_item
-
menu_items_config.find do |entry|
-
entry.is_a?(Hash) && entry[:path].present? && !self.class.drawer_menu_divider?(entry)
-
end
-
end
-
-
1
def data_target_modal
-
"#{id}-data-target-modal"
-
end
-
end
-
-
1
class DrawerEntry < ViewComponent::Base
-
1
with_collection_parameter :attrs
-
-
1
include ActiveLinkTo
-
-
1
attr_reader :title, :path
-
-
1
def initialize(attrs:)
-
else: 0
then: 0
raise ArgumentError,
-
"DrawerItemComponent requires a :path attribute. Attrs: #{attrs}" unless attrs[:path].present?
-
-
@title = attrs[:title]
-
@path = attrs[:path]
-
@visible = attrs.fetch(:visible, true)
-
@disabled = attrs.fetch(:disabled, false)
-
@active = attrs.fetch(:active, :inclusive)
-
end
-
-
1
def title
-
then: 0
else: 0
@_title ||= @title.respond_to?(:call) ? instance_exec(&@title) : @title
-
end
-
-
1
def classes
-
[active_classes, disabled_classes].compact.join(" ")
-
end
-
-
1
def active?
-
# FIXME: document active states / possible values. We should always forward @active, but i think this
-
# may break some existing code. is_active_link? supports boolean
-
then: 0
else: 0
active_value = @active.respond_to?(:call) ? instance_exec(&@active) : @active
-
then: 0
else: 0
return active_value if active_value == true || active_value == false
-
-
is_active_link?(path, :inclusive)
-
end
-
-
1
def active_classes
-
then: 0
else: 0
active? ? "lui-sidebar__drawer__entry--active" : ""
-
end
-
-
1
def disabled_classes
-
then: 0
else: 0
@disabled ? "lui-sidebar__drawer__entry--disabled" : ""
-
end
-
-
1
def path
-
then: 0
@_path ||= if @path.nil? || @disabled
-
"#"
-
else: 0
else
-
then: 0
else: 0
@path.respond_to?(:call) ? instance_exec(&@path) : @path
-
end
-
end
-
-
1
def visible?
-
# If @visible is callable, execute it in the context of this instance
-
then: 0
@_visible ||= if @visible.nil? # default visible
-
true
-
else: 0
else
-
then: 0
else: 0
@visible.respond_to?(:call) ? instance_exec(&@visible) : @visible
-
end
-
end
-
-
1
def render?
-
visible?
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar # Will be class
-
1
module V1
-
1
class Item < ViewComponent::Base
-
1
include ActionView::Helpers::UrlHelper
-
1
include Turbo::FramesHelper
-
1
include ActiveLinkTo
-
1
include FaviconAware
-
-
1
COLOR_MAPPING = {
-
impact: "#40A77F",
-
}
-
-
1
VALID_COUNTER_KINDS = [
-
:success,
-
:danger,
-
:warning,
-
:neutral,
-
:informative,
-
].freeze
-
-
1
def self.from_hash(attrs = {})
-
then: 0
else: 0
if attrs.is_a?(Hash) && attrs.key?(:type)
-
return "LooposUi::Sidebar::#{attrs[:type].to_s.camelize}".constantize.new(attrs)
-
end
-
-
then: 0
else: 0
return Divider.new if attrs == :divider
-
-
then: 0
if attrs.key?(:menu_items)
-
DrawerItem.new(attrs: attrs)
-
else: 0
else
-
SingleItem.new(attrs: attrs)
-
end
-
end
-
-
1
attr_reader :icon, :disabled, :image, :tooltip_title, :coming_soon, :highlight
-
-
# TODO: change to LoopComponent
-
1
def initialize(attrs:)
-
@data = attrs
-
-
@title = attrs[:title]
-
@short_title = attrs[:short_title]
-
-
@tooltip_title = attrs[:tooltip_title]
-
-
@icon = faviconize(attrs[:icon])
-
-
@image = attrs[:image]
-
-
@coming_soon = attrs[:coming_soon]
-
-
@counter = attrs[:counter]
-
@counter_kind = attrs.fetch(:counter_kind, :warning)
-
-
# TODO: review docs, this is not documented
-
@active = attrs.fetch(:active, :inclusive)
-
@visible = attrs.fetch(:visible, true)
-
-
@disabled = attrs.fetch(:disabled, false)
-
then: 0
else: 0
if @disabled && @tooltip_title.blank?
-
@tooltip_title = "Disabled"
-
end
-
-
@color = case attrs[:color]
-
when: 0
when Symbol
-
COLOR_MAPPING[attrs[:color]]
-
else: 0
else
-
attrs[:color]
-
end
-
-
@bg_color = attrs[:bg_color]
-
-
@highlight = attrs[:highlight]
-
end
-
-
1
def id
-
then: 0
else: 0
@_id ||= "#{title&.parameterize}_#{self.class.name.parameterize}"
-
end
-
-
1
def visible?
-
# If @visible is callable, execute it in the context of this instance
-
then: 0
@_visible ||= if @visible.nil? # default visible
-
true
-
else: 0
else
-
then: 0
else: 0
@visible.respond_to?(:call) ? instance_exec(&@visible) : @visible
-
end
-
end
-
-
1
def render?
-
visible?
-
end
-
-
1
def disabled?
-
disabled == true
-
end
-
-
1
def active?
-
raise NotImplementedError.new("Implement in subclass")
-
end
-
-
1
def sidebar_item_classes
-
"#{active_classes} #{disabled_classes}"
-
end
-
-
1
def color
-
then: 0
else: 0
@_color ||= @color.respond_to?(:call) ? instance_exec(&@color) : @color
-
end
-
-
1
def bg_color
-
then: 0
else: 0
@_bg_color ||= @bg_color.respond_to?(:call) ? instance_exec(&@bg_color) : @bg_color
-
end
-
-
1
def highlight
-
then: 0
else: 0
@_highlight ||= @highlight.respond_to?(:call) ? instance_exec(&@highlight) : @highlight
-
end
-
-
1
def title
-
then: 0
else: 0
@_title ||= @title.respond_to?(:call) ? instance_exec(&@title) : @title
-
end
-
-
1
def sidebar_item_styles
-
s = StringIO.new
-
then: 0
else: 0
s << "color: #{color}; border-color: #{color};" if color.present?
-
then: 0
else: 0
s << "pointer-events: none;" if coming_soon
-
then: 0
else: 0
s << "background-color: #{bg_color};" if bg_color.present?
-
s.string
-
end
-
-
1
def active_classes
-
then: 0
else: 0
active? ? "lui-sidebar__item--active" : ""
-
end
-
-
1
def disabled_classes
-
then: 0
else: 0
disabled? ? "lui-sidebar__item--disabled" : ""
-
end
-
-
1
def icon_classes
-
"lui-sidebar-item__icon #{icon}"
-
end
-
-
1
def short_title
-
then: 0
@_short_title ||= if @short_title.respond_to?(:call)
-
else: 0
instance_exec(&@short_title)
-
then: 0
elsif @title.respond_to?(:call)
-
instance_exec(&@title)
-
else: 0
else
-
@short_title || @title
-
end
-
end
-
-
1
def tooltip_title
-
then: 0
else: 0
@_tooltip_title ||= @tooltip_title.respond_to?(:call) ? instance_exec(&@tooltip_title) : @tooltip_title
-
end
-
-
1
def render_counter?
-
then: 0
else: 0
counter&.positive?
-
end
-
-
1
def counter
-
then: 0
@_counter ||= if @counter.respond_to?(:call)
-
instance_exec(&@counter)
-
else: 0
else
-
@counter
-
end
-
end
-
-
1
def counter_kind
-
@_counter_kind ||= begin
-
then: 0
kind = if @counter_kind.respond_to?(:call)
-
instance_exec(&@counter_kind)
-
else: 0
else
-
@counter_kind
-
end
-
-
then: 0
else: 0
VALID_COUNTER_KINDS.include?(kind) ? kind : :warning
-
end
-
end
-
end
-
end
-
end
-
end
-
<turbo-frame id="lui-sidebar" target="lui-main-layout" data-controller="sidebar">
-
<aside class="lui-sidebar">
-
<div class="lui-sidebar__item-list">
-
<% items.each do |item| %>
-
<%= render(item) %>
-
<% end %>
-
</div>
-
</aside>
-
</turbo-frame>
-
1
module LooposUi
-
1
class Sidebar
-
1
module V1
-
1
class Sidebar < LoopComponent
-
1
option :config, default: -> { LooposUi.config.sidebar }
-
-
1
class << self
-
1
attr_writer :configuration
-
-
1
def configuration
-
@configuration ||= ::LooposUi::Sidebar::Configuration.new
-
end
-
-
1
def configure
-
yield configuration
-
end
-
end
-
-
# TODO: test check visible?
-
# TODO: update documentation, active no longer necessary, explain :exact, :icluded, :exculed
-
1
def items
-
item_config_list.map do |attrs|
-
Item.from_hash(attrs)
-
end
-
end
-
-
1
private
-
-
1
def item_config_list
-
then: 0
else: 0
then: 0
if config.items&.any?
-
config.items
-
else: 0
else # backward compatibility to old initializer
-
(config.top_items || []) + (config.bottom_items || [])
-
end || []
-
end
-
end
-
end
-
end
-
end
-
<%= tag.div(id: id, class: "w-full", data: { action: "mouseleave->sidebar#hide mouseenter->sidebar#show" }) do %>
-
then: 0
else: 0
<%= render LooposUi::Tooltip.new(title: tooltip_title, position: :right) if tooltip_title.present? %>
-
<%= link_to path, data: { action: "mouseover->drawer#hide", turbo_action: "advance" } do %>
-
<div class="lui-sidebar__item <%= sidebar_item_classes %>" style="<%= sidebar_item_styles %>">
-
then: 0
else: 0
<% if highlight %>
-
<div class="lui-sidebar__highlight">
-
<span class="lui-sidebar__highlight lui-sidebar__highlight--animation"></span>
-
<span class="lui-sidebar__highlight lui-sidebar__highlight--circle"></span>
-
</div>
-
<% end %>
-
then: 0
<% if image.present? %>
-
<%= image_tag(image, class: "h-4") %>
-
else: 0
<% else%>
-
<%= content_tag(:icon, "", class: icon_classes) %>
-
<% end %>
-
<div class="lui-sidebar__item__short-title">
-
<%= tag.p(short_title, class: "lui-sidebar__item__label") %>
-
then: 0
else: 0
<%= render LooposUi::Counter.new(count: counter, kind: counter_kind, size: :tinny) if render_counter? %>
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Sidebar
-
1
module V1
-
1
class SingleItem < Item
-
1
def initialize(attrs:)
-
super(attrs: attrs)
-
-
@path = attrs[:path]
-
end
-
-
1
def active?
-
then: 0
@is_active ||= if disabled?
-
false
-
else: 0
else
-
then: 0
else: 0
active_value = @active.respond_to?(:call) ? instance_exec(&@active) : @active
-
then: 0
else: 0
return active_value if active_value == true || active_value == false
-
-
is_active_link?(path, active_value)
-
end
-
end
-
-
1
def path
-
then: 0
@_path ||= if @path.nil? || disabled?
-
"#"
-
else: 0
else
-
then: 0
else: 0
@path.respond_to?(:call) ? instance_exec(&@path) : @path
-
end
-
-
then: 0
else: 0
raise ArgumentError, <<~ERROR if @_path.nil?
-
Path is nil, check your sidebar configuration. Bad data at item with config: #{@data}"
-
ERROR
-
-
@_path
-
end
-
end
-
end
-
end
-
end
-
<%= tag.div(class: classes) do %>
-
<%= tag.span(title, class: bem_element_class(:title)) %>
-
<%= tag.div(class: bem_element_class(:items)) do %>
-
<% items.each do |item| %>
-
<%= render Item.new(item: item) %>
-
<%# render LooposUi::MIcon.new(item.icon, size: 16) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.a(
-
id: id,
-
then: 0
else: 0
href: drawer? ? nil : path, # drawer items are not links
-
class: classes,
-
data: {
-
controller: "lui--sidebar-item",
-
"lui--sidebar-target": :item,
-
then: 0
**( if !drawer?
-
{ action: "click->lui--sidebar#resetActiveItems click->lui--sidebar-item#activate" }
-
else: 0
else
-
{ action: "click->lui--sidebar#expand click->lui--sidebar#resetExpandedDrawers click->lui--sidebar-item#toggleDrawer" }
-
end),
-
turbo_frame: "lui-main-layout",
-
turbo_action: "advance"
-
}
-
) do %>
-
<%= render LooposUi::Tooltip.new(title: item.title) %>
-
<div class="flex gap-1">
-
<%= tag.span(class: bem_element_class(:icon_container)) do %>
-
then: 0
else: 0
<%= render LooposUi::MIcon.new(
-
:arrow_right,
-
tag: :button,
-
class: bem_element_class(:drawer_icon)
-
) if drawer? %>
-
<%= render LooposUi::MIcon.new(
-
#item.icon,
-
:home,
-
class: bem_element_class(:icon)) %>
-
<% end %>
-
<%= tag.span(item.title, class: bem_element_class(:title)) %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<%= tag.div(id: drawer_id, class: bem_element_class(:sub_items)) do %>
-
<% item.items.each do |item| %>
-
<%= render self.class.new(item: item) %>
-
<% end %>
-
<% end if item.items.any? %>
-
<%= tag.aside(
-
id: "lui-sidebar",
-
class: classes,
-
data: {
-
controller: "lui--sidebar",
-
turbo_frame: "lui-main-layout"
-
}) do %>
-
<%= tag.div(id: "lui-sidebar-active-item-id", class: "hidden", data: { "lui--sidebar-target": "activeItemId" }) %>
-
<%= tag.div class: bem_element_class(:app_selector) do %>
-
<%= tag.span(
-
render(LooposUi::AppLogo.new(app: LooposUi.config.app_type),
-
), class: "lui-sidebar__app_logo_condensed") %>
-
<%= tag.span(
-
render(LooposUi::AppLogo.new(app: LooposUi.config.app_type, expanded: true),
-
), class: "lui-sidebar__app_logo_expanded") %>
-
<%= tag.div(
-
class: bem_element_class(:toggle_button),
-
data: { action: "click->lui--sidebar#toggleExpand" }) do %>
-
<%= render(LooposUi::MIcon.new(:left_panel_close, tag: :button)) %>
-
<%= render(LooposUi::Tooltip.new(title: t(".toggle_sidebar"))) %>
-
<%end %>
-
<% end %>
-
<%= tag.main(class: bem_element_class(:groups)) do %>
-
<% groups.each do |group| %>
-
<%= render group %>
-
<% end %>
-
<% end %>
-
<div>
-
<%= render LooposUi::DummySlot.new(text: "Business Slot", classes: "h-[200px]") %>
-
</div>
-
<footer class="flex items-center justify-between">
-
<%= render LooposUi::UserMenu.sidebar %>
-
<%= tag.div(class: "lui-sidebar__language_selector") do %>
-
<%= render LooposUi::Button.new(text: current_locale, type: :tertiary, size: :small, kind: :neutral) do |button| %>
-
<%= button.with_leading_icon(icon: :language) %>
-
<% end %>
-
<% end %>
-
</footer>
-
<% end %>
-
1
module LooposUi
-
1
class Sidebar
-
1
module V2
-
1
class Sidebar < LoopComponent
-
1
option :config, default: -> { LooposUi.config.sidebar }
-
-
1
option :expanded, Types::Bool, default: -> { false } # FIXME: does not work
-
1
mod :expanded
-
-
1
def expanded?
-
then: 0
else: 0
@expanded ? :expanded : :condensed
-
end
-
-
1
def groups
-
config.groups.map do |group|
-
Group.new(**group)
-
end
-
end
-
-
1
def current_locale
-
then: 0
else: 0
::LooposUi.config.sidebar.foo.dig("language", "current_locale")&.upcase || "EN"
-
end
-
-
1
def active_item
-
flat_items = groups.flat_map do |group|
-
group.items.flat_map do |item|
-
[
-
::LooposUi::Sidebar::V2::Sidebar::Group::Item.new(item: item),
-
*item.items.map { |i| ::LooposUi::Sidebar::V2::Sidebar::Group::Item.new(item: i) },
-
]
-
end.each do |item|
-
item.instance_variable_set(:@view_context, view_context)
-
end
-
end
-
then: 0
else: 0
then: 0
else: 0
flat_items&.find(&:active?)&.id
-
end
-
-
1
class Group < LoopComponent
-
1
class TItem < Dry::Struct
-
1
attribute :title, Types::Coercible::String
-
1
attribute :icon, Types::Coercible::Symbol.default(:home)
-
1
attribute? :path, Types::String | Types::Interface(:call)
-
1
attribute :items, Types::Array.of(TItem).default([])
-
-
1
attribute? :active, Types::Bool |
-
Types::Symbol.enum(:exclusive, :inclusive, :exact) |
-
Types::Instance(Regexp) |
-
Types::Array.of(Types::Array.of(Types::Symbol)) | Types::Hash
-
end
-
-
1
option :title, Types::Coercible::String
-
1
option :items, Types::Array.of(TItem), default: -> { [] } # TODO: add type
-
-
1
def render?
-
items.any?
-
end
-
-
1
class Item < LoopComponent
-
1
include ActiveLinkTo
-
-
1
option :item, TItem
-
-
1
option :active, Types::Bool, default: -> { false }
-
1
mod :active
-
-
1
def id
-
@id = "lui-sidebar-item-#{item.title}" # FIXME: title is not unique enough
-
end
-
-
1
def additional_classes
-
[
-
bem_element_class(:drawer, condition: drawer?),
-
]
-
end
-
-
1
def drawer?
-
item.items.any?
-
end
-
-
1
def drawer_id
-
"#{random_id}-drawer"
-
end
-
-
1
def path
-
else: 0
@path ||= case item.path
-
when: 0
when String
-
item.path
-
when: 0
when Proc
-
instance_exec(&item.path)
-
end
-
end
-
-
1
def active?
-
path.present? && is_active_link?(path, item.active)
-
end
-
end
-
end
-
-
1
def before_render
-
return
-
config.items = [
-
{
-
title: "Overview",
-
items: [
-
{
-
title: "Home",
-
icon: :home,
-
},
-
{
-
title: "Submenu",
-
icon: :face,
-
items: [
-
{
-
title: "Submenu Item 1",
-
icon: :face,
-
},
-
{
-
title: "Submenu Item 1",
-
icon: :face,
-
},
-
],
-
},
-
],
-
},
-
{
-
title: "Overview 2",
-
items: [
-
{
-
title: "Foo",
-
icon: :home,
-
},
-
{
-
title: "Submenu bar",
-
icon: :face,
-
items: [
-
{
-
title: "Submenu Item 2",
-
icon: :face,
-
},
-
{
-
title: "Submenu Item 2",
-
icon: :face,
-
},
-
],
-
},
-
],
-
},
-
]
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class SidebarLayoutComponent < ViewComponent::Base
-
1
renders_one :top_navigation, "SidebarOldComponent"
-
1
renders_one :bottom_navigation, "SidebarOldComponent"
-
-
1
def initialize(app_logo: nil, app_url: nil)
-
@app_logo= app_logo
-
@app_url= app_url
-
end
-
-
1
class SidebarOldComponent < ViewComponent::Base
-
1
def initialize(menu: [])
-
else: 0
then: 0
@menu = map_menu_items(menu) unless menu.empty?
-
end
-
-
1
private
-
-
1
def map_menu_items(menu)
-
menu.map { |item_hash| create_menu_item(item_hash) }
-
end
-
-
1
def create_menu_item(item_hash)
-
then: 0
if item_hash[:subitems].present?
-
LooposUi::MenuItem::DrawerItem.new(item_hash: item_hash)
-
else: 0
else
-
LooposUi::MenuItem::SingleItem.new(item_hash: item_hash)
-
end
-
end
-
end
-
end
-
end
-
<% @menu.each do |item| %>
-
then: 0
else: 0
<%= render item if item.can_view? %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
# TODO: move to Sidebar namespace. Should become LooposUi::Sidebar
-
1
class SidebarOldComponent < ViewComponent::Base
-
1
attr_reader :config
-
-
1
def initialize(config: LooposUi.config.sidebar)
-
@config = config
-
end
-
-
# TODO: test check visible?
-
# TODO: update documentation, active no longer necessary, explain :exact, :icluded, :exculed
-
1
def items
-
item_config_list.map do |attrs|
-
LooposUi::Sidebar::Item.from_hash(attrs)
-
end
-
end
-
-
1
private
-
-
1
def item_config_list
-
then: 0
else: 0
then: 0
if config.items&.any?
-
config.items
-
else: 0
else # backward compatibility to old initializer
-
(config.top_items || []) + (config.bottom_items || [])
-
end || []
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Spinner < LoopComponent
-
1
option :theme, Types::Coercible::Symbol.enum(:light, :dark), default: -> { :dark }
-
1
option :size, Types::Coercible::Symbol.enum(:tiny, :small, :default), default: -> { :default }
-
-
1
def spinner_json
-
then: 0
if @theme == :dark
-
<<~JSON.squish
-
{"nm":"Composição 1","ddd":0,"h":200,"w":200,"meta":{"g":"@lottiefiles/toolkit-js 0.67.2","tc":"#ffffff"},"layers":[{"ty":4,"nm":"4_layer_ring","sr":1,"st":17,"op":2267,"ip":17,"ln":"24","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.9647,0.9647,0.9647]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":17},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":17},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":42}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":1},{"ty":4,"nm":"3_layer_ring","sr":1,"st":13,"op":2263,"ip":13,"ln":"22","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.9059,0.9059,0.9059]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":13},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":13},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":47}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":2},{"ty":4,"nm":"2_layer_ring","sr":1,"st":9,"op":2259,"ip":9,"ln":"20","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.5333,0.5333,0.5333]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":9},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":9},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":52}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":3},{"ty":4,"nm":"1_layer_ring","sr":1,"st":6,"op":2256,"ip":6,"ln":"16","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.4275,0.4275,0.4275]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":6},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":56}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":4},{"ty":4,"nm":"bg_ring","sr":1,"st":0,"op":2250,"ip":0,"ln":"14","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.2392,0.2392,0.2392]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":5}],"v":"5.7.0","fr":25,"op":57,"ip":0,"assets":[]}
-
JSON
-
else: 0
else
-
<<~JSON.squish
-
{"nm":"Composição 1","ddd":0,"h":200,"w":200,"meta":{"g":"@lottiefiles/toolkit-js 0.66.4","tc":"#ffffff"},"layers":[{"ty":4,"nm":"4_layer_ring","sr":1,"st":17,"op":2267,"ip":17,"ln":"24","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","bm":1,"nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.2706,0.2706,0.2706]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":17},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":17},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":42}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":1},{"ty":4,"nm":"3_layer_ring","sr":1,"st":13,"op":2263,"ip":13,"ln":"22","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","bm":1,"nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":80},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.3647,0.3647,0.3647]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":13},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":13},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":47}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":2},{"ty":4,"nm":"2_layer_ring","sr":1,"st":9,"op":2259,"ip":9,"ln":"20","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","bm":1,"nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":50},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.3647,0.3647,0.3647]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":9},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":9},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":52}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":3},{"ty":4,"nm":"1_layer_ring","sr":1,"st":6,"op":2256,"ip":6,"ln":"16","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[178.72],"t":31},{"s":[357.4],"t":56}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","bm":1,"nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":30},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.3647,0.3647,0.3647]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":-89},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"tm","nm":"Aparar caminhos 1","e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[4],"t":6},{"s":[100],"t":31}]},"o":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":6},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":31},{"s":[100],"t":56}],"x":"var $bm_rt;\n$bm_rt = content('Aparar caminhos 1').start;"},"m":1}],"ind":4},{"ty":4,"nm":"bg_ring","sr":1,"st":0,"op":2250,"ip":0,"ln":"14","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[97.5,98,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"shapes":[{"ty":"gr","nm":"Retângulo 1","it":[{"ty":"rc","nm":"Caminho do retângulo 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":999},"s":{"a":0,"k":[121.283,121.283]}},{"ty":"st","nm":"Traçado 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":20},"c":{"a":0,"k":[0.9059,0.9059,0.9059]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[124.669,124.669]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":5}],"v":"5.7.0","fr":25,"op":57,"ip":0,"assets":[]}
-
JSON
-
end
-
-
end
-
-
1
def spinner_classes
-
166
"lui-spinner lui-spinner--#{@size} lui-spinner--#{@theme}"
-
end
-
-
1
def size_px
-
166
when: 66
case @size
-
66
when: 62
when :tiny then 12
-
62
when :small then 14
-
else: 38
else
-
38
16
-
end
-
end
-
end
-
end
-
<%# Lottie spinner commented out becuase it breaks in production for some reason %>
-
<%
-
=begin%>
-
<div class="<%= spinner_classes %>"
-
data-controller="lui--spinner"
-
data-lui--spinner-data-value='<%= spinner_json %>'
-
>
-
<canvas data-lui--spinner-target="canvas"></canvas>
-
</div>
-
<%
-
=end%>
-
166
<%= render LooposUi::MIcon.new(:progress_activity, class: spinner_classes, size: size_px, class: "animate-spin") %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class StateLabel < LooposUi::Label
-
# TODO: this should be stored in a shared colors file
-
1
class << self
-
1
alias_method :c, :find_color
-
end
-
-
COLORS = {
-
# text, background
-
1
success: [c("general-global-white"), c("general-success-800")],
-
danger: [c("general-global-white"), c("general-danger-800")],
-
warning: [c("general-global-white"), c("general-notice-800")],
-
neutral: [c("general-global-white"), c("general-gray-900")],
-
informative: [c("general-global-white"), c("general-informative-800")],
-
draft: [c("general-gray-900"), c("general-gray-200")],
-
manager: [c("apps-manager-800-primary"), c("apps-manager-200")],
-
core: [c("apps-core-800-primary"), c("apps-core-200")],
-
hubs: [c("apps-hubs-800-primary"), c("apps-hubs-200")],
-
submission: [c("apps-submission-800-primary"), c("apps-submission-200")],
-
validation: [c("apps-validation-800-primary"), c("apps-validation-200")],
-
handling: [c("apps-handling-800-primary"), c("apps-handling-200")],
-
default: [c("general-gray-900"), c("general-gray-200")],
-
}
-
-
LIGHT_COLORS = {
-
# text, background
-
1
success: [c("general-success-800"), c("general-success-200")],
-
danger: [c("general-danger-800"), c("general-danger-200")],
-
warning: [c("general-notice-800"), c("general-notice-200")],
-
neutral: [c("general-global-black"), c("general-gray-200")],
-
informative: [c("general-informative-800"), c("general-informative-200")],
-
draft: [c("general-gray-900"), c("general-gray-200")],
-
white: [c("general-global-black"), c("general-global-white")],
-
default: [c("general-gray-900"), c("general-gray-200")],
-
}
-
-
# Reference the App colors since they are using the neutral
-
1
LIGHT_COLORS[:manager] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:core] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:hubs] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:submission] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:validation] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:handling] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:exits] = LIGHT_COLORS[:neutral]
-
1
LIGHT_COLORS[:impact] = LIGHT_COLORS[:neutral]
-
-
1
attr_accessor :text, :icon, :count, :condensed
-
-
1
def initialize(text: nil, icon: nil, color: nil, light: true, count: nil, condensed: false)
-
246
super(text: text, icon: icon, color: color, count: count, condensed: condensed)
-
-
246
color = color.to_sym
-
-
246
then: 246
else: 0
@text_color, @bg_color = light ? LIGHT_COLORS[color] : COLORS[color]
-
-
# If the color is not defined, use the default color
-
246
then: 0
else: 246
@text_color, @bg_color = LIGHT_COLORS[:default] if @text_color.nil? || @bg_color.nil?
-
246
then: 0
else: 246
raise "No color defined for: #{color}" if @text_color.nil? || @bg_color.nil?
-
end
-
-
1
def styles
-
246
else: 62
then: 184
return super unless @color == :draft || @color == :white
-
-
62
then: 0
if @color == :draft
-
<<~CSS.squish
-
#{super}
-
border: 1px dashed var(--General-Gray-900, #495057);
-
else: 62
CSS
-
62
then: 62
else: 0
elsif @color == :white
-
<<~CSS.squish
-
62
#{super}
-
border: 1px solid var(--General-Gray-200, #ECEFF2);
-
CSS
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class StatusDot < LoopComponent
-
1
option :kind, Types::Coercible::Symbol.enum(:success, :informative, :warning, :error, :apps), default: -> { :success }
-
-
1
def status_dot_classes
-
"lui-status_dot lui-status_dot--#{@kind}"
-
end
-
end
-
end
-
<%= tag.div class: status_dot_classes %>
-
1
module LooposUi
-
1
class Stepper < LoopComponent
-
1
option :data, type: Types::Strict::Hash, default: -> { {} }
-
1
option :active_step, type: Types::Strict::Integer, default: -> { 0 }
-
-
1
renders_many :steps, ->(*args, **kwargs) {
-
@step_count ||= 0
-
step = LooposUi::Stepper::Step.new(*args, **kwargs, index: @step_count, stepper: self)
-
@step_count += 1
-
step
-
}
-
-
1
renders_one :header, LooposUi::Header
-
-
1
class Step < LoopComponent
-
1
option :id, Types::Coercible::String, optional: true
-
1
option :index, Types::Integer
-
1
option :stepper, Types::Instance(LooposUi::Stepper), optional: true
-
-
1
option :state, Types::Symbol.enum(:previous, :current, :next), default: -> {
-
else: 0
then: 0
raise "Default state only supported if stepper present, pass in the state" unless stepper.present?
-
-
case index
-
when: 0
when ...stepper.active_step
-
:previous
-
when: 0
when stepper.active_step
-
:current
-
else: 0
else
-
:next
-
end
-
}
-
-
1
option :active, Types::Bool, default: -> { state == :current }
-
-
1
mod :open, condition: -> { (state == :current) && active }
-
1
mod :state
-
1
mod :active
-
-
1
renders_one :header, ->(**args) { LooposUi::Header.new(**args, size: :small) }
-
-
1
class << self
-
1
def previous_action_button(*_args, **kwargs)
-
LooposUi::Button.new(
-
**deep_merge_args(
-
kwargs,
-
{
-
# tag: :button,
-
kind: :neutral,
-
type: :secondary,
-
size: :tiny,
-
full: true,
-
text: I18n.t(".back"),
-
# tag_options: {
-
# type: :submit,
-
# data: { action: "stepper#previous" },
-
# },
-
},
-
),
-
)
-
end
-
-
1
def next_action_button(*_args, **kwargs)
-
LooposUi::Button.new(
-
**deep_merge_args(
-
kwargs,
-
{
-
# tag: :button,
-
kind: :neutral,
-
type: :primary,
-
size: :tiny,
-
full: true,
-
text: I18n.t(".next"),
-
# tag_options: {
-
# # type: :submit,
-
# # data: { action: "stepper#next" },
-
# },
-
},
-
),
-
)
-
end
-
end
-
-
1
renders_one :previous_action, ->(*args, **kwargs) {
-
self.class.previous_action_button(*args, **kwargs)
-
}
-
-
1
renders_one :next_action, ->(*args, **kwargs) {
-
self.class.next_action_button(*args, **kwargs)
-
}
-
-
1
def before_render
-
else: 0
then: 0
raise "Header slot is required" unless header.present?
-
end
-
-
1
def index_data(index)
-
{
-
"stepper-index-value": index,
-
}
-
end
-
end
-
end
-
end
-
<%= tag.div id: id, class: classes, data: { stepper_target: :step, controller: "lui--step" } do %>
-
<%= tag.div class: bem_element_class(:indicator_wrapper) do %>
-
<%= tag.div class: bem_element_class(:indicator), data: index_data(index) do %>
-
<%= tag.span(index + 1, class: bem_element_class(:indicator_number)) %>
-
<%= render LooposUi::MIcon.new(:check, class: bem_element_class(:indicator_icon), size: 14)%>
-
<% end %>
-
<%= tag.div class: bem_element_class(:indicator_line) %>
-
<% end %>
-
<%= tag.div class: bem_element_class(:body) do %>
-
<%= tag.div class: bem_element_class(:header) do %>
-
<%= header %>
-
<% end %>
-
then: 0
else: 0
<%= tag.div class: bem_element_class(:content), data: {"lui--step-target": "content"} do %>
-
<%= content %>
-
<% end if active %>
-
<%= tag.div class: bem_element_class(:buttons) do %>
-
<%= previous_action %>
-
<%= next_action %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= tag.div class: classes, data: { controller: "stepper" } do %>
-
<%= header %>
-
<%= tag.div class: "lui-stepper__container" do %>
-
<% steps.each do |step| %>
-
<%= step %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
class StepperPanel < LoopComponent
-
1
renders_one :stepper, LooposUi::Stepper
-
1
renders_one :top
-
1
renders_one :bottom
-
1
renders_many :actions, types: {
-
button: { renders: Button, as: :button_action },
-
modal_button: {
-
renders: ->(*args, **kwargs) {
-
modal = LooposUi::Modal.new(*args, **kwargs)
-
-
stepper_panel_instance = self
-
with_trigger_button_method = ->(*args, **kwargs) {
-
super(*args, **deep_merge_args(kwargs, stepper_panel_instance.default_trigger_args))
-
}
-
modal.define_singleton_method(:with_trigger_button, with_trigger_button_method)
-
-
modal
-
},
-
as: :modal_action,
-
},
-
action_menu: {
-
renders: ->(*args, **kwargs) {
-
action_menu = LooposUi::ActionMenu.new(*args, **kwargs)
-
-
stepper_panel_instance = self
-
with_trigger_button_method = ->(*args, **kwargs) {
-
super(*args, **deep_merge_args(kwargs, stepper_panel_instance.default_trigger_args))
-
}
-
action_menu.define_singleton_method(:with_trigger_button, with_trigger_button_method)
-
-
action_menu
-
},
-
as: :action_menu_action,
-
},
-
}
-
-
1
def default_trigger_args
-
{
-
kind: :neutral,
-
type: :secondary,
-
size: :small,
-
}
-
end
-
end
-
end
-
<div class="lui-stepper_panel">
-
<%= top %>
-
<div class="w-full grow overflow-y-auto relative">
-
<%= stepper %>
-
<div class="bottom-0 left-0 right-0 h-10" style="position: sticky; background: linear-gradient(180deg, rgba(252, 252, 252, 0.00) 0%, var(--surface-secondary-soft, #FCFCFC) 100%)"></div>
-
</div>
-
<%= bottom %>
-
then: 0
else: 0
<%= tag.div class: "lui-stepper_panel__actions" do %>
-
<% actions.each do |action| %>
-
<%= action %>
-
<% end %>
-
<% end if actions.any? %>
-
</div>
-
<%# DEPRECATED: Use LooposUi::V2::Table instead %>
-
-
<%
-
path = URI.parse(link).path
-
per_page_parsed = Rack::Utils.parse_nested_query(URI.parse(link).query)
-
prev_link_parsed = Rack::Utils.parse_nested_query(URI.parse(link).query)
-
next_link_parsed = Rack::Utils.parse_nested_query(URI.parse(link).query)
-
pagy = @pagy
-
link = @link
-
link_data = @link_data
-
link_parameters = @link_parameters || {}
-
link_remote ||= @link_remote || false
-
%>
-
-
<% link_per_page = capture do %>
-
<%= link_to "teste!", "#{path}?#{Rack::Utils.build_nested_query(per_page_parsed.merge(link_parameters.merge(page: pagy.prev)))}", method: @method, data: link_data, remote: link_remote %>
-
<% end %>
-
<% link_to_back = capture do %>
-
<%= link_to "", "#{path}?#{Rack::Utils.build_nested_query(prev_link_parsed.merge(link_parameters.merge(page: pagy.prev)))}", method: @method, data: link_data, remote: link_remote %>
-
<% end %>
-
<% link_to_next = capture do %>
-
<%= link_to "", "#{path}?#{Rack::Utils.build_nested_query(next_link_parsed.merge(link_parameters.merge(page: pagy.next)))}", method: @method, data: link_data, remote: link_remote %>
-
<% end %>
-
-
-
<% pagy.series.each do |item| %>
-
then: 0
else: 0
<% if item.is_a?(String) %>
-
then: 0
<% if pagy.series.last == item && item != '1' %>
-
else: 0
<%= react_component("Pagination",{defaultValue: params["per_page"], app: @app, totalItems: pagy.series.last , itemsRange: item, onPagePerPage: link_per_page, onPagePrevious: link_to_back, onPageNext: link_to_next, isDisabledPagePrevious: false, isDisabledPageNext: true } )%>
-
then: 0
<% elsif pagy.series.last == item && item == '1' %>
-
else: 0
<%= react_component("Pagination",{defaultValue: params["per_page"], app: @app, totalItems: pagy.series.last , itemsRange: item, onPagePerPage: link_per_page, onPagePrevious: link_to_back, onPageNext: link_to_next, isDisabledPagePrevious: true, isDisabledPageNext: true } )%>
-
then: 0
<% elsif item == "1" %>
-
<%= react_component("Pagination",{defaultValue: params["per_page"], app: @app, totalItems: pagy.series.last , itemsRange: item, onPagePerPage: link_per_page, onPagePrevious: link_to_back, onPageNext: link_to_next, isDisabledPagePrevious: true, isDisabledPageNext: false } )%>
-
else: 0
<% else %>
-
<%= react_component("Pagination",{defaultValue: params["per_page"], app: @app, totalItems: pagy.series.last , itemsRange: item, onPagePerPage: link_per_page, onPagePrevious: link_to_back, onPageNext: link_to_next, isDisabledPagePrevious: false, isDisabledPageNext: false } )%>
-
<% end %>
-
<% end %>
-
<% end %>
-
-
# DEPRECATED: Use LooposUi::V2::Table instead
-
-
1
module LooposUi
-
1
module Table
-
1
class PaginationComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
attr_reader :pagy, :link, :link_data, :app, :method, :link_remote, :link_parameters
-
-
1
def initialize(pagy:, link:, link_data:, app: "manager", method: :get, link_remote: false, link_parameters: {})
-
@pagy = pagy
-
@link = link
-
@link_data = link_data
-
@app = app
-
@method = method
-
@link_remote = link_remote
-
@link_parameters = link_parameters
-
end
-
end
-
end
-
end
-
<%# DEPRECATED: Use LooposUi::V2::Table instead %>
-
-
<%= turbo_frame_tag @turbo_frame do %>
-
<% extra_params = params.to_unsafe_h.except('format', 'controller', 'action') %>
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
<div class="table<%= @small_table ? '__small' : '' %> <%= @collapsible ? 'collapsible-table' : '' %> <%= !@show_footer ? 'no-footer' : '' %> <%= !@show_header ? 'no-header' : '' %>" data-controller="table-component">
-
<table id="rules">
-
then: 0
else: 0
<% if @show_header %>
-
<thead class="copy-12-medium">
-
then: 0
<% if !head? %>
-
<tr>
-
<% default_table_headers.each do |header| %>
-
<td>
-
<%= header[:label].html_safe %>
-
then: 0
else: 0
<% if header[:sort_attribute].present? %>
-
<%= render 'loopos_ui/sort', attribute: header[:sort_attribute], link_parameters: @pagination_parameters.merge(turbo_stream: @pagination_turbo), link_method: @pagination_method %>
-
<% end %>
-
</td>
-
<% end %>
-
</tr>
-
else: 0
<% else %>
-
<%= head %>
-
<% end %>
-
</thead>
-
<% end %>
-
<tbody id="rules_collection" class="copy-14-medium">
-
then: 0
<% if body? %>
-
else: 0
<%= body %>
-
then: 0
else: 0
<% elsif has_partial_and_data? %>
-
<%= render partial: @data_partial, collection: @data, as: model_element_name %>
-
<% end %>
-
then: 0
else: 0
<% if @data.blank? %>
-
<tr>
-
<td colspan="<%= default_table_headers.size %>" class="table__no-results">
-
<%= I18n.t("pages.app_instances.no_data") %>
-
</td>
-
</tr>
-
<% end %>
-
</tbody>
-
</table>
-
</div>
-
then: 0
else: 0
<% if @show_footer %>
-
<div id="rules_pagination" class="table__pagination-wrapper">
-
else: 0
then: 0
<% unless @add_rows.blank? %>
-
<%= button_to @add_rows[:url], method: :post, params: {parent_element_id: @add_rows[:parent_element_id]} do %>
-
<i class="fa-solid fa-plus"></i>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if @show_pagination %>
-
<%= render LooposUi::Table::PaginationComponent.new(
-
pagy: @pagy,
-
link: url_for(**extra_params.except('page')),
-
link_data: { turbo_frame: @turbo_frame, turbo_action: @turbo_action, turbo_stream: @pagination_turbo },
-
method: @pagination_method,
-
link_remote: @pagination_remote,
-
link_parameters: @pagination_parameters,
-
app: @app
-
) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
# DEPRECATED: Use LooposUi::V2::Table instead
-
1
module LooposUi
-
1
module Table
-
1
class TableComponent < ViewComponent::Base
-
1
include Turbo::FramesHelper
-
-
1
renders_one :head
-
1
renders_one :body
-
-
1
def initialize(
-
turbo_frame:,
-
turbo_action:,
-
pagy:,
-
model_class:,
-
headers: [],
-
data: [],
-
data_partial: nil,
-
namespace_prefix: "",
-
model_element_name: nil,
-
app: "manager",
-
show_pagination: true,
-
pagination_method: :get,
-
pagination_turbo: false,
-
pagination_parameters: {},
-
small_table: false,
-
show_header: true,
-
show_footer: true,
-
collapsible: false,
-
add_rows: {}
-
)
-
super
-
@turbo_frame = turbo_frame
-
@turbo_action = turbo_action
-
@pagy = pagy
-
@model_class = model_class
-
@headers = headers
-
@namespace_prefix = namespace_prefix
-
@data = data
-
@data_partial = data_partial
-
@model_element_name = model_element_name
-
@app = app
-
@show_pagination = show_pagination
-
@pagination_method = pagination_method
-
@pagination_turbo = pagination_turbo
-
@pagination_parameters = pagination_parameters
-
@small_table = small_table
-
@show_header = show_header
-
@show_footer = show_footer
-
@collapsible = collapsible
-
@add_rows = add_rows
-
end
-
-
1
def default_table_headers
-
@headers.map do |header|
-
then: 0
else: 0
next { label: header.humanize } if header.is_a?(String)
-
-
header
-
end
-
end
-
-
1
def path_helper_method
-
"#{@namespace_prefix}#{@model_class.model_name.route_key}_path"
-
end
-
-
1
def path_helper(*args)
-
send(path_helper_method, *args)
-
end
-
-
1
def model_element_name
-
@model_element_name || @model_class.model_name.element
-
end
-
-
1
def has_partial_and_data?
-
@data_partial.present? && @data.present?
-
end
-
-
1
def show_paginator?
-
then: 0
else: 0
(body? || has_partial_and_data?) && @pagy&.pages.to_i > 1
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class TableDataList < LoopComponent
-
1
renders_many :groups, "LooposUi::TableDataList::Group"
-
-
1
private
-
-
1
def number_of_columns
-
then: 0
else: 0
groups.map(&:number_of_columns)&.max || 0
-
end
-
-
1
class Group < LoopComponent
-
1
erb_template <<-ERB
-
<%= header %>
-
<% rows.each do |row| %>
-
<%= row %>
-
<% end %>
-
ERB
-
-
1
renders_one :header
-
1
renders_many :rows, "LooposUi::TableDataList::Row"
-
-
1
def number_of_columns
-
then: 0
else: 0
then: 0
else: 0
rows.map(&:cells)&.map(&:size)&.max || 0
-
end
-
end
-
-
1
class Row < LoopComponent
-
1
erb_template <<-ERB
-
<% cells.each do |cell| %>
-
<%= cell %>
-
<% end %>
-
ERB
-
-
1
renders_many :cells
-
end
-
-
1
class LabelHeaderRow < LoopComponent
-
1
renders_one :label
-
1
renders_one :description
-
end
-
end
-
end
-
<span class="lui-table_data_list__label_header_row">
-
<%= label %>
-
<%= description %>
-
</span>
-
<% groups.each do |group| %>
-
<% capture do %>
-
<%= group %>
-
<% end %>
-
<% end %>
-
-
<%
-
columns = [
-
{
-
title: "Fake header column",
-
dataIndex: :row_0,
-
},
-
*(number_of_columns-1).times.map do |i|
-
{
-
title: "Fake column #{i+1}",
-
dataIndex: "row_#{i+1}",
-
}
-
end
-
]
-
-
args = {
-
columns:,
-
show_pagination: false,
-
searchable: false,
-
tree_indent_size: 0,
-
}
-
%>
-
-
<div class="lui-table_data_list">
-
<%= render LooposUi::V2::Table.new(**args) do |table| %>
-
<% groups.each_with_index do |group, i| %>
-
<% table.with_row(key: "group_#{i}") do |row| %>
-
<% row.with_cell(property: "row_0") do %>
-
<%= group.header %>
-
<% end %>
-
-
<% group.rows.each_with_index do |group_row, j|%>
-
<% row.with_child(key: "group_#{i}_#{j}") do |child_row| %>
-
<% group_row.cells.each_with_index do |cell,k| %>
-
<% child_row.with_cell(property: "row_#{k}") do %>
-
<%= cell %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class TableFilter < LoopComponent
-
1
option :base_key, Types::Coercible::String, optional: true
-
1
option :key, Types::Coercible::String
-
1
option :text, Types::Coercible::String
-
-
1
option :visible, Types::Bool, default: -> { false }
-
1
def classes
-
then: 0
else: 0
"#{super} #{visible ? "flex" : "hidden"}"
-
end
-
-
1
def value
-
last_key = key.split("$").last
-
then: 0
else: 0
then: 0
else: 0
last_key == "empty" ? "#{base_key&.humanize}: -" : text
-
end
-
end
-
end
-
<div class="<%= classes %>"
-
data-table-filters-target="filter"
-
data-key="<%= key %>"
-
>
-
<div class="lui-table_filter__text">
-
<%= value %>
-
</div>
-
<div class="lui-table_filter__close" data-action="click->table-filters#delete">
-
<%= content_tag(:icon, "", class: "fa-regular fa-xmark fa-xs") %>
-
</div>
-
</div>
-
<%= turbo_frame_tag "#{frame_id}_extra_show", data: { controller: "tab-skeleton", "turbo-action": "advance" } do %>
-
<div class="loopui-tabs__wrapper" id="<%= frame_id %>_tab" role="tablist">
-
<% @tabs.each_pair do |tab_link, icon_class| %>
-
<%= link_to link_url(tab_link), class: "tab-disabled", data: { action: "click->tab-skeleton#setLoading", "tab-skeleton-target": "button", app: @app } do %>
-
<%= react_component("Tab", { text: tab_name(@resource, tab_link), variant: @app, isActive: @tab.to_s == tab_link.to_s, icon: icon_class }) %>
-
<% end %>
-
<% end %>
-
</div>
-
<%= turbo_frame_tag "tabs_content" do %>
-
<div class="loopui-tabs__content" id="tabs-container" data-tab-skeleton-target="container">
-
<%= render partial: "#{@relative_path}/#{@tab}", locals: { partner: @partner } %>
-
</div>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module TabsComponent
-
1
class TabsComponent < ViewComponent::Base
-
1
include Turbo::StreamsHelper
-
1
include Turbo::FramesHelper
-
-
1
attr_reader :resource, :tab_param, :tabs, :url, :relative_path, :app, :extra_params
-
-
1
def initialize(resource:, tab_param:, tabs:, url:, relative_path:, app: "manager", use_resource_in_url: true,
-
extra_params: {})
-
@resource = resource
-
@tab = tab_param.presence || tabs.keys.first
-
@tabs = tabs
-
@relative_path = relative_path
-
@url = url
-
@app = app
-
@extra_params = extra_params
-
@use_resource_in_url = use_resource_in_url
-
end
-
-
1
def link_url(tab_link)
-
then: 0
new_url = if @url.is_a?(String)
-
else: 0
URI.parse(@url + "?"+ { turbo_frame: "tabs_content", tab: tab_link }.to_param)
-
then: 0
elsif @use_resource_in_url
-
URI.parse(@url.call(resource, turbo_frame: "tabs_content", tab: tab_link))
-
else: 0
else
-
URI.parse(@url.call(turbo_frame: "tabs_content", tab: tab_link))
-
end
-
-
new_params = Rack::Utils.parse_nested_query(new_url.query || "").merge(@extra_params)
-
new_url.query = Rack::Utils.build_nested_query(new_params)
-
new_url.to_s
-
end
-
-
1
def frame_id
-
then: 0
else: 0
@resource.respond_to?(:id) ? @resource.id : @resource
-
end
-
-
1
private
-
-
1
def tab_name(resource, tab_link)
-
name = tab_link.to_s.dup
-
name = name.split("/").last.titleize
-
name.delete!("_")
-
-
name
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class TabsContent < LoopComponent
-
1
renders_many :rows, "RowLayout"
-
-
1
class RowLayout < LoopComponent
-
-
1
erb_template <<~HTML
-
6
<% columns.each do |column| %>
-
6
then: 0
else: 6
<div class="column <%= column.half ? 'column--half' : '' %>">
-
6
<%= column %>
-
</div>
-
<% end %>
-
6
HTML
-
1
renders_many :columns, "ColumnLayout"
-
-
1
class ColumnLayout < LoopComponent
-
7
option :half, optional: true, default: -> { false }
-
-
1
def call
-
6
content
-
end
-
end
-
end
-
end
-
end
-
6
<div class="lui-tabs_content">
-
6
<% rows.each do |row| %>
-
6
<div class="row">
-
6
<%= row %>
-
</div>
-
<% end %>
-
6
</div>
-
1
module LooposUi
-
1
class TabsLayout < LoopComponent
-
3
option :keep_tab_in_url, default: -> { true }
-
1
option :active_tab_key, optional: true
-
-
1
include LooposUi::FaviconAware
-
-
1
renders_many :tabs, ->(**args, &block) do
-
6
@tabs_count ||= 0
-
6
@tabs_count += 1
-
6
@active_tab_index ||= 0
-
-
# Check if this tab should be active based on active_tab_key
-
6
if active_tab_key.present?
-
then: 0
# activa_tab_key has priority over the tab active flag
-
args[:active] = false
-
-
then: 0
else: 0
if Tab.keyfy(args[:key]) == Tab.keyfy(active_tab_key)
-
args[:active] = true
-
@active_tab_index = @tabs_count - 1
-
else: 6
end
-
6
then: 0
else: 6
elsif args[:active]
-
@active_tab_index = @tabs_count - 1
-
end
-
-
6
LooposUi::TabsLayout::Tab.new(index: @tabs_count - 1, **args, &block)
-
end
-
-
1
renders_one :impact, ->(**args, &block) do
-
LooposUi::TabsLayout::Tab.new(index: @tabs_count, **args, impact: true, &block)
-
end
-
-
1
def initialize(...)
-
2
super(...)
-
2
@tabs_count = 0
-
2
@active_tab_found = false
-
end
-
-
1
class Tab < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
option :index
-
1
option :title
-
7
option :key, default: -> { title }
-
1
option :icon, optional: true
-
7
option :active, optional: true, default: -> { false }
-
1
option :url, optional: true
-
7
option :disabled, optional: true, default: -> { false }
-
7
option :lazy_load, optional: true, default: -> { false }
-
7
option :load_only_on_click, optional: true, default: -> { false }
-
1
option :impact, optional: true
-
-
1
attr_reader :index
-
-
1
def initialize(**data)
-
6
then: 0
else: 6
data[:key] = data[:key].to_s.parameterize.underscore if data.key?(:key)
-
6
super(**data)
-
end
-
-
1
def turbo_frame_args
-
args = {
-
6
data: {
-
tabs_target: "panel",
-
tab_id: index,
-
},
-
hidden: !active,
-
}
-
-
6
then: 0
else: 6
if url.present?
-
then: 0
if lazy_load
-
args.deep_merge!({
-
data: {
-
lazy_load: lazy_load,
-
url: url,
-
load_only_on_click: load_only_on_click,
-
},
-
})
-
else: 0
else
-
args.deep_merge!({src: url})
-
end
-
end
-
-
6
args
-
end
-
-
1
class << self
-
1
def keyfy(key)
-
24
key.to_s.parameterize.underscore
-
end
-
-
1
def turbo_id(key)
-
6
"lui-tab-#{keyfy(key)}"
-
end
-
end
-
-
1
def turbo_id
-
6
self.class.turbo_id(key)
-
end
-
-
1
def classes
-
6
then: 0
if active
-
else: 6
"lui-tabs_layout__tab_entry--active"
-
6
then: 0
elsif disabled
-
"cursor-default! opacity-40 hover:text-general-global-black"
-
else: 6
else
-
6
"text-general-global-black"
-
end
-
end
-
-
1
def key
-
18
self.class.keyfy(@key)
-
end
-
-
# Helper component. Meant to be used in turbo_stream responses. On being rendered,
-
# it will emit an event to request focus on the tab with the given key.
-
1
class RequestFocus < LoopComponent
-
1
option :key
-
end
-
end
-
end
-
end
-
<%= tag.span(
-
data: {
-
controller: "tab-focus",
-
tab_focus_tab_key_value: key
-
},
-
style: "display: none;"
-
) %>
-
12
<%= turbo_frame_tag turbo_id, class: "lui-tabs_layout__tab", data: {}, **turbo_frame_args do %>
-
6
then: 0
<% if url.present? %>
-
<%= render LooposUi::TabsContent.new do |tabs| %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %><%= render LooposUi::Loadings::Skeleton.new %><% end %>
-
<% end %>
-
<% tabs.with_row do |row| %>
-
<% row.with_column do %><%= render LooposUi::Loadings::Skeleton.new %><% end %>
-
<% end %>
-
<% end %>
-
else: 6
<% else %>
-
6
<%= content %>
-
<% end %>
-
<% end %>
-
2
<%= tag.div(
-
class: "lui-tabs_layout",
-
data: {
-
controller: "tabs",
-
tabs_keep_tab_in_url_value: keep_tab_in_url,
-
}
-
2
) do %>
-
<div class="lui-tabs_layout__tab_list" data-tabs-target="tabList" data-controller="lui--blurred-scroll">
-
2
<% tabs.each_with_index do |tab, i| %>
-
6
<%= tag.span(
-
data: {
-
6
then: 0
else: 6
action: tab.disabled ? nil : "click->tabs#changeTab",
-
tabs_target: "tab",
-
tab_id: i,
-
tab_title: tab.key,
-
tab_key: tab.key,
-
load_only_on_click: tab.load_only_on_click
-
},
-
6
class: "lui-tabs_layout__tab_entry #{tab.classes} #{(@active_tab_index == i) && "lui-tabs_layout__tab_entry--active"}"
-
6
) do %>
-
6
then: 0
else: 6
<%= tag.i(class: faviconize(tab.icon)) if tab.icon %>
-
6
<%= tab.title %>
-
<% end %>
-
<% end %>
-
2
then: 0
else: 2
<% if impact.present? %>
-
<%= tag.span(
-
data: {
-
then: 0
else: 0
action: impact.disabled ? nil : "click->tabs#changeTab",
-
tabs_target: "tab",
-
tab_id: tabs.length,
-
tab_title: impact.key
-
},
-
class: "lui-tabs_layout__tab_entry #{impact.classes} lui-tabs_layout__tab_entry--impact"
-
) do %>
-
<svg width="75" height="22" viewBox="0 0 75 22" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-
<rect y="0.5" width="75" height="21" fill="url(#pattern0_249_146)"/>
-
<defs>
-
<pattern id="pattern0_249_146" patternContentUnits="objectBoundingBox" width="1" height="1">
-
<use xlink:href="#image0_249_146" transform="matrix(0.000917431 0 0 0.00327654 0 -0.0258847)"/>
-
</pattern>
-
<image id="image0_249_146" width="1090" height="321" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABEIAAAFBCAMAAABEsPpCAAADAFBMVEVMaXEQICYRl4wQICYQICYQICYQICYQICYQICYQICYQICYQICYVn4sQICYQICYQICYkw4QoyoMQICYguIYQICYs04Ijv4UQICYQICYQICYQICYQICYQICYQICYQICYQICYQICYnyIMQICYQICYv2oAQICYpy4Mv2oAnyYMiv4Uv24As0oIbr4gr0YIYp4kcsIg05n4VoIooy4MhvIYs1IEy4n8ZqokSmowkw4QivoUhu4YWooo0534mx4QkwYUx3oAWpIocsYghvIYpzoM05X8lxIQetYcs0oIjwIUVoIolxIQTnYsXpoofuIYt1YE05X4oy4MnyYMWo4olw4QVoYoWo4o05n4lxYQqz4Iu14Eu2IEivoU05n42634lxIQWo4omx4Qu2IEft4YpzYM47X0kwYUnyIQz5H8s04Ift4Yftocx34AWooo37X0Unos05X8s0oInyIMSmowVoYoSmYwguoYUnos05X4u2IEYqIky4H8fuIc05n405X8SmowVoooy4n8fuIY37H03630u14E37H037H0153405n405X4z438qz4IXpYoy4H805H847X0s1IIarYghvIY47H026X4aqokXpokguIcqz4It1oEv2oEnyIMjwIU26X4x3oAQICYnx4QivYUlxIQmxYQmxoQUnoslw4QUnYsTnIss0oITm4sjvoUSmYwr0IIjv4URmIwt1IEt1YEhuoYr0YIu14EguYYu2IEs04IkwYUSmoskwIUqz4InyIMVn4soyoMqzoIivIYoyYMhu4YguIYpzYMu1oEpzIMVoIo15n41534y4H805H8ftocetIc26H405X4v2YA26X4ZqokbrYgarIknyIQSmowVoYos04EivIUnyYMWoooz438iu4YWo4oXpIoz4n83630Rl4wt1oE37X0YpokYp4kYqIkft4Yoy4MZqYkes4cx3oAv2oAx3YAlwoQcsIgw24Adsoccr4gbrogw3IAdsYckwoU26n4v2YEXpYoy4X836n4z4X8aq4gw2oBipTVSAAAAn3RSTlMAIYBEdxDwQMCAYNCAoDDgQMBmgJmAgLCIcJDMUO4z3VUQEaqAu4DAQDBAMEBgQMAQQCBgwCDAQFDwoGCgwNBQEIAgoGCgQNDgoMDAwPCwMJDQ4PDAUHCA8KDwwMDQcCCwkOBwgJDw0OCwwIAwQNBAQODw8CDQsODgsPBwgPCQcLCQ8IBwkODwsJDw4JDggLDw8JDw8JC3SLda2sq85Om+qbhCAAAACXBIWXMAACxKAAAsSgF3enRNAAAgAElEQVR4nO2dZ5xc13mf7wgzOzO72l0QBAXuYlEWFDpBAwQhVAIkQBAgFYIoLGIDCVIkRUrsJqlCkerFlC1aTj6PI9npRbZlp/dCKU7k0E6iJHbiFFOMFTu9OoqT385Oved9z3lPuf3/fAP2zr3n3DnvM6efqBjc/tRTH+xy11NP7S5ImgEAOWD3sQ/e/4XfGueBr34QIgEAGDn2+Qd+i+OBzx+DRgAALE8d+al/beCrd+3D+wMAqOz+4BdM/ljmqzfh7QEAxtl35DMygSzxmc+iKgIAGLL7iNwfyxyBRAAAPT5rUQMZSgRdqwCApU5UYR9InM98Fm8PgMqz+/M/dOaB26v+9gCoOrd/wd0gP/zhD1ERAaDS3PS/PHkA3aoAVJcjb3nzB59C+QGgmuy+398gb731FmaaAVBJdj8QxCBvvfV5lB8AqsfuB74biiMoPgBUjYAGgUMAqBxBDfLd796FEgRApfjSd8KCPlUAqsSXAxvkO1/ERFUAqsNNoQ3yne+8jFV3AFSF27/47fB8CcUHgIrweAIG+fa30R0CQDV4JRGDfPuLaMoAUAVu/xUZj9//5VdeeeXI/S8Lr/8VzA4BoAo8LrDB43c9M3wTu5955UsihzyD8gNA6bnJaIKXqYMejh35pPGD96P0AFB2dn/yl/W8zHaL3vSy4aO/jGoIAGXnJ/QS+KR2qvozBomgGgJAyTFUQu43bUJ2l/7zqIYAUG70lZCfMOd93+O6G2BQBoBy82O/wfNjsslhRzS3+A3MDQGgzNykM4h0pZzOIVj1D0CZuV8T/fK1thqHPI7iA0B52feveGyWuHyKvw0OhQCgvNzFh/6XbTK9+8eDmAgAUCweZyPfsgHyDHujT6FIAFBW9v1fFtv2x6e4G30cpQeAsnLX/+H4sG2O97G3wg6IAJSVT3Fh/3H76RzsvTCsC0BZ+fjfY7CuhETRMe5emKAKQEm5nYt6h0oI7yMstQOgpNwUtOJwhLsbig8A5eTDXNA79YCyLRlMLgOgnGxlYv7HnXK7j1PIDhQfAErJ479Dc49bZj/O3O4YSg8ApYQJ+d9xnJO+lbmdw/AOAKAAcApxnAx2CQoBoErs+A8Mju/gw8ztoBAASgmnELfeVF4hW1F8ACgjnEJcQz70/QAAuWbHf6dxVkjg+wEAoBAoBICyAoUAADzY8Z9pXEP+WOD7AQByTWiFvBcKAaBKpKWQ96JUAVBGdvw/GleFXGLuVwWFdBSaOUgVAImy43dpXBWylbkfFAJAKQmtkHuZ+1VhpS4UAorDzh3H3mvFrh30jh2BFbKPud3vVmG/ECgEFIGduy5tvfcfurH10q6dsSzuYG7kqJBjXLqqsGsZFALyzs7PnXG1x4B7z+wa3Vc5sELu4R5bhbIFhYBcs/PSR/93IE4NLbKDuaOjQu4Ne7tiAYWA/LLv0kd/KSj39Fo0O5ibusX8MS6JjtsoFgsoBOSVHVvD+mNZEt0ezrAKOcM97HNVKFtQCAhCO16OWp533RW4AjImkaAK2cc+Kt6LW0qgEBCEwAo59tFvJcfWfTuYezspZCuX0HsrUbSgEBCEoArZyUZlIM4EVAino29960wlihYUAoIQUCGLlxIWCI+DQhb5+tKuShQtKAQEIZxCdn7072TGdfbJPcOn9dpKFC0oBAQhmEI+l51AXBRyD3+zU9UoWVAICEIghSye+bdZYq2QezSJrUY7BgoBYQijkMVTP5cplgpZvEeX2MVqFC0oBAQhiEJ2vpGtQSwVslMrvEpMTYVCQChCKCRzg9gp5Ar9vSoxrwwKAaEIoJCdb3wzaywUsutD+rQ6DO4UEygEBMFfITkwiDjuFz9nEMg3v/lsVcoVFAKC4K2QxVPpWEKLSCGLx+4JdKdSAIWAIHgr5Lp/mgOMgb/47BWyhFamEgKFgDD4KuSKP5cHPnSFjjPXvSFNY1WGY6AQEApPhTybC4ME442KzAmJoBAQCj+FLH7oj5eKKhz+0AcKAUHwU8ilchmkGqv8e0AhIAheCnlWHJzXXXdJ210xzpnr3khUFQwfqlAzBgoBgfBSyKlfEPD62V0Oq+ev3XXiA5K7B+QDVZmXugwUAoLgo5Bd5vA99YjH5hvHrktVIRVZodsHCgFB8FHI66agPOH7u37tiXTs8QvVMwgUAsLgoZBdf1rPiRC7fz17yvCUUFxRtQIFhYAgeChEH9zXhdo+8IpUDHKicsUJCgFBcFfIs9qQfCTc17Pz9eQNUrVWDBQCQuGukBOaiAw7uLGYeGOmggaBQkAYnBWy+Jd4TgWeYLF4SvMwfyo2mtsDCgFBcFbILj5yXw8+RStRh1xWqRllA6AQEARnhZxhQzKJH/XF1xMzyJaKFiQoBATBWSF/jSWRjoWdH+Af6MNllWzERFAICIWrQg6wYZvQWrVHkhDIBwIOHBUNKAQEwVUhZ9mwTOo4ycvCC2RLNXtBloFCQBBcFXLubzMkNkfrWu6JjlRbIFAICISrQtiwTe5M67MhBfL6I9UWCBTSp9FcaLfnWz3WtNsLyb6HZrPdbvcf15pfel4j0QdGUa052W5Pj2RxslkLd3dHhWzjQvOhcEmLsxhOICcOJJfMopCAQmaG0THvFIojZb1b0BOOrcZCuzWhvodOZ3bN3EwCj2u2W7PU4zoTrfZCMnmdmZxvkY/sTE3PhXGlo0Ie+bsMScbmCe6hdjy0q+oVkC5hFdKYnJ6iiumkNDIaC1RZn0gklrvMtIkEjz56eiHk42pzTCgPX1Y7cFYbk9OkIMder391xFEhZ7mg9U6Qhp3++jh3FvWPHmqB4hQy04qhFPXJNXwxXTMpSIzuBrPzVqFlTm03uvT+6EXYdKA2jexxnc7sXLC6SEP3SkeZMlo+/kJjKBWrCcMHWq0lyVzGhOhlod4AyTkfe1x2Ysu2RFNXMNTCxMVLU39ho236rWvrC6nxBp0piYZkqZU90OnJHLV58eM6nekgvRQzpvrHKBOGZ8rvJGYpmH+bJtmpnluYp/722S1adm3bhrZLHPXbdlPIgqCwTsxpEjInKe2z4lA2K2TSIrxsnkzTmLZ5WhCJNE1tJrtn2t5MQBRFXCwn+zu/LZvHlhP1e3ZRSENaXebK6Iysit/pkA0Sy9R2U2wdX7M+zRl5hWeIodZmegHWGVximn+my+0MaBSS3JBuF+6xVVys74v6HTsoZIYeYSCYoH/MbWoEbVGODQppOoR0p+VaMWiK388Ys+4dqzWh0xX4mqLjDXVE0c6/yuCccxmXMY+1aT8tbjuwZcuJywJyYsuWA8VrKalfsL1CrJoE88Stld58LS3Jr7NeIXYPHKBtifHMuz2t0+m4Pc85f12mGHF53JIjirYxofyYY8alnPBTyLYtJzgJBWCpv7ZAIlG/XGuFTNqVm2nlzrbdBJJfZ22zy/aBQ9bYNy5q0jYahaZhwSNuFdIwovS6Jw2vkGQHZKJoC/Pcs8ZPLh44ey45ewx57qFHCrL2V/1qbRVi/Rsbd4h9QE+YHaJRSMMnwqwbF05NpiFT9g6Z83pghxOl911Vomjb36BJWiG73J6785FzzAcT4bGHfE7PSQv1e7VUiGUdpKM4xKVKYHYIrxAvg3Q6HbuhGYfXM46tQzyqWEOoxkyA28bJTiEuz7327HNp+qPHc2fzXhlRv1c7hTiFyKhD3JrtRoewCvE1iF0HhbdBbB3in70uxPsNct9xomjbP6dJXCG2z13cdY75SPI8lm+LqN+rlUJm3Krpw1/yBcfSN2EILE4hIUJM7c3hCGAQu8NpHb8PlQmlGAS68ShRtO0v01xvkWcXLJ977cnHmA+kxHMX89uiUb9XK4W4BmT/R67hXOSn9PniFOI62DmG1CFBDEIPYtEEMwjRYAt35wEFUci2h7L1xzIn8zrtTf1ebRTi3HM326tEeES0fn4IoxC273eqNd1epkUv2x1DFtQzulu05tvN3mL7WrPZ5lbSdpGu9JMYpL8+xSj/eFvGfGtromjbP6ZJXCHy5267nrk2dc7lc+Kb+q2LFTJFBNvUUmA0l7bDaM5po2JZAEQzZnZNe6HZbEQzzeZke40uJrTdIbRCyGbT1Pxk7FaNpv7Jsj5VvoY1O0+95OY8NwPN1GrrYTDI1Pzk2GMbzcl5nUhiDtHe240o2vbTNEnPC+Geqyhk+/XMlZnwXB4lon71YoUoTCu7XSzw67y6P8DxmJlSVp3PzLEzO7V9BKRCiKBWnyhIu2xLBM6gmoW/C8xnRC2nhm4O7BpmJa5uKe/s2Ec0N3clirZzgSjJsAdChSyezJNAfjqfElG/fEeFMCvUG5NMwZ5W+wqYVV7sag9dVYBUSDxaTItTNYvzJ4yT3ZmhJsMjm/QjJetz+MTqdw9osJYek3RTjzKWPGX4QLe2GnGhknDvoUwhFx9LzgXOPJe3PhF5adUqRLOSv8HE0kJzfOnIGj62FugyPqvJF6WQ+P8J1rExEW3uzmU6QgRr9cjXJRiVYeeDCBYZc56XrUeiky0bSOICKeE4kShk+7l8uWPA9fkanVGLjYtC9AvQJKvwJrRdhsxKYM1nlNROt9vjydAoaxR2AZChS5WsOonmx5PzWY3q4UZ/mIWNMRpMP7N8Lq6jQs79DM3d4gc7sY157IhC7mYuyQGPJfx27JAXVo1CTD9W5skYxvlTZBHXlFJTz41eWWOJ5zoLtFFNRrRwdJbqFjUFZI0xnXhRDz0R31TXGuKokOvNsZwERoVs59yWD85tT/b92CCPDD4ozb9zJocIugvJ8WO+ImFQiGi1bx+mIjKruQc5GiOeGk85xFBnovuL5KLkviNxU8ZRIY9yESlPuAsmhVx8LNcGSb6aZoFaaKwVIhrg1DpENOBAtfX5ueZ6hcgna3VhRks14UV1aFh0LBB1GH2S6Qk63Lp9GvI7MncbczmWKeTiNxiS3d14JfPUW7t/XTzJpSpH3JqXHhG10NgqRPbbqptmJZzsSe0Nz16sVYj1JoaMAdnwoioh8mnx5JQ7Xd8xMwXFeoUelUtpsh0VwsXyN07apd0SrUK2nyuAQb7xjQs52UNeLTOWCpH+tvLL6aQlnbIQ+1GdQhy2QaUdwkYJUSmQ9ypEtBN0NQpyNMZ+rxEyl8KajOtpdmyEJPojq1PIyguJRX1g8tGYUYuMnULka8DYqZfi2jZhIbatr1GI00bKtEO4d6XmVdwg6KHmVePqGpU2O2f1buTQkcslWPi5o19nSDQ+VjIPvTWK9nIJyiEn87C5mTwsyKC0iAxu1FG+dp74aWbjyqfzV/pwNkyIh9tuYag+TqMEqi/VYa8i5rXJvmJXhTzKBeaFJKshvELuLpBBvv71ozlwiFpirBRi0UXIVEO0bfwY6sguW05ZhVh1SYxC9ubQL0ttV9hXCdS8WqXMttbTh6jpyd6Yq0IOsOGR4Km6vEJOJhbtyZADh0ijgg5K4fqvZejeEJtKgRorE9ylnEJcqvc9qFEPOrzUCov9ARJqXtl7UD0hzidWEA020ZfsqpDoj7Ak2F24kn9qwTid+QwReeGjgtKmEkL/jNtUQqiKDHcloxDXH+cuVHuBup+6Hthm16A+Sl65thDVE2I5aj2CayPMWSEPsSF7IbnoKI9CknxLMtQCY6MQu4CkWjJ2fQRq7d5y/Mj1VIUuVGcjJVG1VuBSJ1DyymmBqN3ZidmUetHdnBWyl4+O5GrpYRRy4daTd19cGYyLd5+89YJDKo4n9ZZkqMXPIijX2D2LqnDbdfqpv+92CvEJLXmwKgt8nR6rdD9zMUmY2efgPaJSIxkzc1bI4n/kScwhKzUPlXHr3SsTSdziyosnT9slJeP+EHnxI4LScnCDiEBLCanF204hnkf0UztykOFVmxuziFPdR2n3Md04/mqPoZpe0ixyVkh08n/yJNXSX6l5ppnTj65MJll9Fg+cPG2RnmwdIg8zoqhadiwEGOtUbsD1xpAKcemSGMNi4nltZC8ftw4Y5VH0ZUTdzu9Eb9XTkj5od4Ws/PMaLuz1yovTM/VceDSdzofjF4+K05Tk4JURtfzJFWJbPyfC2vZgJ2W8wEohFovOGNTeEM1L6O8I5jgKpDyLvkytGTmPW/dQu40FzU13hUSn9eGRRFPfWSG3JqM0muMX9a9myKMppiqOGmdyhVgXVfVhtndQSreNQjx7QiK6Lab9ye9axHF4RMkrGcjEOJdfJYSqawlarB4K2asPjwvvCV9Pd1TIrQk3YIiEnpSlLMP1MmoBlCvEuomv3MG6ZeGlEK/hmGWIvkZjeC04HtCv5JX8ZtTZKt7NtYZyS4EEPRQSnf6Leg4+GromstLwRJL7UhfIEsffc1CQtoPZDe2qISFXiHXnpHIH63qMl0J8f50jcuaVb7OBRaYQtdXhOIN/BGWdsEAHPgrZKwjfvUEt4qCQ05kIZIlFiUSOZpU6L4VYh6RyB6upaZGnQjwmpg5Rf/QDNI9oZApRekzYCbty1EyaP+qjkOioJIaPPnogmEbsFfKeUI92YfGkOYGZdYeokSZXiP/DrH8wfRQSoB1DtmSc1rMJEClE7QrxG9HtEn95U4KalpdCVv4ZKffd954QHBI/cJmjWU8B3X7UmMasaklqRKSpEOumkI9CHLskYqgDIJ6TTVhECnHq+jQyvNvsmrYsf14KiWxDOmUyrYL0uHjQkOfTGc0OUSNNfpqd/8PSVEiACn5ETsMIUrshEClEHSIK1uMz0Wo35TUsP4UsmuIjSw5m1gsyhrEikpHoPBRi3/OfqUICVPAj8mfffUmbHpFClIuCdM1Mt9oLvnsk2RWPA38yt+RhT45lDhleUTaLZaqjEOuuWxq188F7EJVBpBBlhCixASIDngqJvpZXgxzKjUGiaK8+qfdlkqjqKMR/auoyyo2DjPQQiBSipCaQKa3xVUh0NC0n2HEoo/dJs/2gNvGZtLiqo5AwvanUzBCbT9eazbYQpeOWeFvqLLCkOndNeCtk8fQfziH5MsiSQ3Tv6MUsklQdhVinVpoE4Z1rC+2W4GRQDcTbCjBZJxDeCjFERzbkzSCmt5TmCp4+aoEtqUICDchQ2x6Zhy0aC9N+9uDeVnKmtMVfITl0SP4MYnhLpzNIkKigdim4QoJ1eqrDqKZcLHCn8loiUUgwU9oSQCG5c0geDWJ4Sxkst1MLMRSix1IhDbVPwxXiOSECNwxBUrL94D/LEdkMcJjZq3lFGaQZCrHFarlho82cuO9C+RUSbX/xr+eGrGZ7mvma5hWlPxNfVFC7FFwhwSaA2ShkLqBAKqGQaPG+vBgkw+XzRjQvKf3Gl6igdim4QoLNl5ArZIY+zNuZKigkii7/F/kgi8ENKccPsq/oYOp1J7U4QyF6xArhTgB1phoKiW5++G/mgCfCvp7AfIJ/QamrTy3OUIgeqULI0/a9qIhCosWvZW+Q9H/M7biPTXnq08vUIg2F6JEppBFoINfwnFIqJIpufvHnMybDvUhFbOdfT9qL7UQFtQsUwtyZnHgeuBuEe05JFRJFex/OVCH5bsYscYhN+ydSTomooHaBQpg7U7kI34qpmEIylkjGZ0wKOM6mPe2WjKigdsG8EObORC7USfAhqJZComjvk7+YEZcHeSXJcoh9NSn7DwqxRd2bWMmFevTvCBOt6XArdUutkCja/unbsnDIbTnvS+1ynE1+ymMyahGHQvSYJ7g32Alls/ML8q0EJfuFKAPHie0nbyIpmR04lL5FilAJiaInuOSn3JGjFvOSKiRYbKkKie9EwnSETMzbbVkiUUipVupybL/8yb+VJoWohETRAe6V3JZuOtSSjv1C9KijtbHLiSMqlwTStj0swk0hSR1JYSLhJtX2vZ9+8raUNFKMSkgUse8j3an5alkvq0JCxZZxyyFyRsga+8dLFKIea1PYXcsk3HzzgctDwdds8j8cs8ynuQykO6yrFvayKiRUbCk3jjWRiMOqOhMuG7e67Z0a4hgZF/LTsSvkiX/HkM9dQgi25yMHanEvq0ICHfeiGiL2HogB3QmnfVuVTlnqbSkXJXUkhYnCKeQ2LgDzPjF1CJeFh1NNhVrey6qQQKcjqAO2sRsTOwy57fwselvKCwkTuW2LkaPeJ5JJSGKwP+Hpxp8Xh7g8pJoKtbyXVSGBzmpQB2TGk0B0prrVf2Sbs6ujP475GmOmexTmnM3XUzSFfOLvM3w6B4kTspfLw81ppkIt8GVVSKD+VLU3dTwT6swzx1iS9eYIJro5MJhu0pqfFG4JXzSFHOLCL8c7DcU5zuUh1f5UNdJKq5AgZ1GpdYPYsQvqeIxjTKvbjVA3UvMZojNkrD9nQnQyd9EU8iITfSlPqvDj4TzUpNSIKK1CgnSGqF0hsT3Tla4Q1zltarcs+baUq0LMoVPWGZs3hi+aQrgf8MKMxyzxBJOJJ9NMhBpppVVIkPMR1L6H2HHfyt9dlwibWkw91F0F/I/tU0edzGeaF0whN3MKyfN+hwqXM5lItUtYjbTSKiRES4ZY/mLqTXXtmxB+M2plxb+2pXawmHuEC6aQvf+AoSjzyrrczOUizUQIC2opFGL+KTVC7Ic6/qMfbGI9sdqXfFtEy8q731it2Zj7VAumkBuZ2CtUV0h0nFPIDSkmQlhQS6GQAOfNqsEVax4FW95HrNWj35Z6ne/uSmo7RpCJginkCSb2Uu1F8IdTSJqjutKCWgqFeA9WmDtpg0USsWEA/bbUISDfaohqL8GLK5hCnmRi78YcpM2CF6EQ2zv4KcS7iq92ccbXpISKJGrXIvptEW0rv2oI0d8j6KEtmEIeZmKvUL2pvAnTnBgiLailUIhvTyM1zBOzUqhIImTFvC1ipoqfKtX5t5LGWMEU8u8ZUp3X6c8TTDbSrExJC2o5FOI54Eksf4lLKVAkkcln3hbRa+LTcUxUQiQz9EuikDT7IQNwIxRiewdfhXgtlFF/ntUcKA0Qt9koVCWEe1uBx68JI0kqNcVSyHFOITlImw1QSOoK8ekmoHYjU6r4YbY6olPPvS3izBr3pozrtN5iKeTm/0JToGW6XW5k8pHmHFt5QS2HQtybMuTpUsoGP+pzXSoERItJ87aow3tdA7hBPFo0Fl4OhfxIDtJmw94c5ENeUEuiEOffZ2o/Q7WVonZuOnThMsfQsG+LMo5jzzHRjJF9z1BIFuQhH/KCWhKFdKbcHEJuy048XbnGvjOEO4aGfVtUNcRtB0TqTrIvqVgKufp/0BRNIXnIh7zElEUhbg4hDTJL3EntCLWN5RnuGBr+bZEn+Do4hOruEY7uQCFZAIVkohAXh9BHw1BBqjZCKNFoqLEHWfFvi86ttUNIeZVyyyEoJBzygloehXSmbPtUaYOQI8REK8RqXv0MWaMwvS06gZYT+kmDSO9RMIX8J5rCKSQH+ZAX1BIppDNh9WByLMZi2ZtNdYBtxejfFnMGZ8umAkQ+Wtz7rCgk0F61yQCFhENeUMukEKv5IQtMVDO/z8TIjfwQiEmNQbRpZrpgLVypbhLSsRmSVufdZXWunoSyKORGKMT2DsEU0pkSPrtBHk6n6eKggll4FFWDOY23n2TdZ7mPTssimcmmfKa8aWf7fHH1f6UpnEKYfGB2Kou7QiZarVh7ZFrSUdhmqwVszYKcFyaJpwV6RtkQXYOIa23JDvNlsmkxiYaY/Z/V2ZwCoJBwyL/4giukRXQYmCTSaPNBzS89o1sExlpPM5a32bb6dN16N74XZaKtz2djjsumxfdDVfum/bdxTQgoJBzyYlN8hRATp1qT/O/szLSmY0JXw2cCUhtQk9QJuuock9np9oB4nYSbkdZNLp/PBT6bNk0RY8tRyV2WXP3faIqmkB9h8gGFsPgphFwAu2aOCO3awrS2VaGdWkJOFu1+iglkKorXcOt1x3Mkem4vn+rbbs6t0WjSbo689uEKUEgQOIXcmWIa5F9uGRTCjH12WtPthWaz2Yhmms1mu90ydUoYZotpQn9qfmHMWM2FNnl19wmWCjE4ZOnprXa7m9O5dnvelEvLUVl+PgsFFBKEn2TycXWKaVC/3TIrxLa6zWAapOVM1Wei1YO/pPtmmOV2Yzkaw+gQC2zn8OoTS+YvO67+NZqiKYTJxq9BISy+CgkSZOZpHt6mmhMklnr/4RxivQqAWl/DA4WE4COcQtJMhPodl1shAYJMMlHM8ym9Xgj1NIZRyPcfyiEO64isWjJQSAjuhEIyUAgz5ipHFlz6SWIGBo/QdobQ7183Q16Oy24jVvbKqULel22ybLkxD9mQf7llUYjnD/Ua4c+zh0OGktI2iJj339B3woqQ7LesYvPgrBXyj2gKppD3Mdn4WJqJkH+5pVGIl0PkUyWozZpFjDaUdFHJvn/nJ/eYdYxvfpsCFSgkBD/JZCPVA7XkX255FOJe2Z+1mW6pXTInfIYuKvn3r9kwQIC0nqWim9wWI2uF/D5NsRTyESYXv384zVTIv9wSKcS1sj9vF1xOkRzratHYTvf++aU9JmY9zo+wqN9BIQF4jVPIR9JMhfzLLZNCnCr7LfsVH/ZPUfLDO0T7/g2Lfvnn+y3Rb0rVBYUE4GOcQlJNhfzLLZdCrKsIs07bGdfsqjvUWjy2xmR4/zUHiYgWL2uRqitjhVzzqzTFUgiTiZRzIf9yS6aQKJo0TWMfYcpJIBGxAFfDBDMQwtzC+P5rds2ZCX+BdJ+qW504AArx505OIX8o1WTIv9zSKSSKJoU1kWmvAi+Lqc6spg1Rm1uj+k7y/ie5DZMUpubC7TK2MN8yZRkK8ecWTiGp9qaWFqHwZuaNVRHdfgBCGuZIXuNczTFQk1hkaj63O3skQxkUcgNnkF99MAepKz7yOtOMZsH77LS/P5ZpLMyzVZ5wT2Fo0quBe/qYngzSgCkU1/yApkgKOczk4Qfnc5C4EmDX7Ko12/Otscr3VGt+rhk6sJtz7fGntFrz7WY6AVxbaE+PLw2eak23J3O8OWGSlEEh52xQ6gUAAApvSURBVDmF3JKDxJWAAD03oLyUQCFcFn7wgzT3GyoxUAjQcM07NAVSyPuYLLzzzg05SF0JgEKAhuIrhMvBO++kusauxEAhQEPxFcJXQjCkGwYoBGi45vs0f6AoL+1OJgPf//730Y4JAxQCNBRdITecZw2CdkwgoBCgoegK+QpfCcF4TCCgEKDhmrdpCqIQLvlvv/025pWFAgoBGoqtkBvO8wp5dw7SVw6gEKCh2Ar5GG+Qt7E+JhRQCNBwze/RFEIhX2ESvwQmtwcDCgEaiqyQwxqD/F6qWx6WGygEaLjmN2kKoJDDTNK7FGZeSwGAQoCG4ipEa5DfvCYHKSwLUAjQUFiF6A2CaWUBgUKAhj3/kuaOnL+015h098BwTECgEKChoAq5RW+Qr+QgieUBCgEaCqmQB5/WG+QFLLALCRQCNOz5dZo8K+TwC0yi+2CVf1CgEKCheAp58CWDQHKtvyIChQANhVPIa6YqyK+/gL7UsEAhQMOe79HkVCF3nmfSOwKaMYGBQoCGQilkzx1mgXzvpRwktFxAIUBDgRRy+GmBQL53HqMxoYFCgIY9b9LkTSEPvvsFJqXjvIDldcGBQoCGQijkhsN3iPzx5ptvoiMkPFAI0JB/hTz42ktSf7z5JrYqSwAoBGjIt0IePHzLebk/3nwT+wwlARQCNOz5KzSZK2TP4Xff8QKTOA4MxiQCFBKc9fX62t7LXFuvrzDevlavb+5dv7ler+UqL5xCzr87O1664w5LdyzzNAZjEgEKCcvG1fEX2lm9SvOE9Vcq119ptk5qcAopIDBIQuRXIfXl9NRzkBQxqzYoQlhiHZeJFZvJ69duzEuG9vxsWXg/DJIQUEg4VqwldLDMBkoKm65ir9+ck/ZMaRSCntTEgEKCoTZJRrlKecx6usrSIx8VkbIoBAZJDigkFHSbZMjaTeMPWr9Of30uMr7nj5YCzChLECgkECaDxB1SMxik09H1wqZFKRTy/J6cl51iA4WEId6tcWW9Xo+3bNaOPinWb1JfInb9+uyzVQaFPP2j2b/HMgOFBGEs+kdGccfHeK9Ucrf838NR3BWj2tmQfb72/7HC82r2b7HcQCEhqI28wKvGB1M2jUpk6IphM+aqWB/JyPXZ573wCjm/P/N3WHagkBCMNGPUkZSNRL1ilUYTKwZ/W5d5xoqukFcxG6TCFEgh66l6xsifh1WOfhNn4Bx1rHfUOZmP7O7/E0XmaVRBKk2BFDJse9CpHTqh36M6kAo5hWxwv9VJplpCkRXyPJb2V5ziKGTTQAhUnSIac8zyKEstrpRxBrWazDtUC6yQW9CGqTrFUciwksFNSx9KZjk7g+6OK+nrByO+m+i/p8b+v1BQbsFILiiOQgYDsVwlZOSS5WrHxnGjKAxqLVlPDSmoQiAQUCiFDOoMfO/nsMO1W69YZVDIYNZI1uv+9/+T4vH8uyAQEBVJIZv68a4bgx20ZLr1ClNDZqCQrCe57/9TRePpw+gDAcs4KKRe7w+WGncLs7g0ilbU6z0FrK6rTYtBDWOz5hZXjUlhoBC6O3VYS4FCrHj+VRzyAAbYKmRVfKHbalYNpkt7Ed7rHK2PL4hbV4/1cQ46NpgqxfJNxpoug4oL01KBQlz8ccudGb8tkC/sFLKKWvi6mQxQ86WjCtlIXD3uClPHxvhFy58d3HQDOeaSH4X82YJw/lX4A8SwUch6br8wdW6W5NIRhdD7CG0Ybc6IOi5WjD9mOFFkbb72W45RCIU8/9Jr6EAFKhYK2RgP8Q4boaJLhwphdwEZGXxxUciKkVvlucM49wp5/6uH0f0BaOQKWdXRsG69/aUDhfCbm474wkUh427K057t4+z/N3nl+fe/9K47YQ+gQayQoRY2DMZLNg4bIOtq1pf2FVKPX7tqpF0zqIc4KWRkZd5yNvNZF/nRd+WQO/fvR8MFmJEqZBCMsaMTBpG91vrS0WZGfPP1gUQGanJSSBTfoiy/GgGgmEgV0t8HXRlSra2N/0V66ahClA7Zwcbr/WkgbgqJ1DOrutnNw46pAJQBoUL6AUxd1+9wqFleOqIQIqI3rR3/m6NCqHpIl6tgEQACIFNIfw0sPa1r7Wh9QX7pUCHk0/sO6S3Fd1UIOeWk99hcj/QCUAhkCuldxUwU7+/LUbO7dBDvzNLb/rWrRpNgrxDd6VWrIREA/JApZMOIIwjqIxUP+aX9eF9nuHbz6D9cFBJt4loz+ThGBoACI1LIem1tYdB42WB3aT/e2UUv/TZRd3a6j0KUcyJG4HcfAQCYESmkdxG/UUcvPtdbXdqPd37Dn9UjN/NUyJJF6uTxuvEjNAEAFogUsjx9VLNRx8ZBdFtc2ot3ptdk5NpuNcVfIUusqquLd1APAcAdkUI2mEJt0yDWLS5dMSII7bXdu4VRSNStjcTGaDDXDABnRAoxxvrQHBaXrjA/esOwnhJOIUs9NuP9qxiXAcAViUJq5muW54xttrm0H++6g6CWr+3ODAmqkNgWRzrjAQC0kSRXiC52V/di3eLSfrzr1s/2ZrNGCShk9HzN7I/EBKCo5Fshq30UMhjG5dopwylnWZ8CAUBhKYxC7Dc+NCpksCAQHaoAuFKYhoz99stmhdQl9wQAmIMu/92p9odAmA+rs+o6AQAQFGZQN+qHu/goqkEdg5XU4NxuKAQARwoztcz+QMzxQ2UooBAAfCnMBHf7Y7lNmwmMKAd9IQA4UphldiMHS3Ddo5sG7ZjYYXbcQjpzPQUAoCfkYv91dpfaLfYfEQR38+F6/mUrDZo+XO4GZ0Tk9ngIAPJOHrYcMlQS+kMwQ0PQqR1WU9aOfZ5NzaClw6YAAGAg5MaH6+0utdv4cKTzk641rB8uelkV/wC5J0htgyEBAAAjAbZf3iTffnmT+/bL4wc6qH0tI/vBb1A/QJypWxvuHIJ2DACu+B8Csanfo7De8lK7QyCWgn5kce1V407YNLqv4QrqA/HkrBr+TTdZDQCgJSdHUSnXru9fO9oiGtvlY/WwJrJxbI/2K5kP1IeVjdrYHohYZAeAM9kfiLliBXEts4g2vofylfV6Pb47+1gvTPzE76Xr45uWYVIIAO7wxyOMxF2U5LHcK3pxTh/LHWvijB3VTzLec7pJ3So1DvpSAfBArBD9levGKguySwcK2UTurD7y6BFMDomPvRgdgo4QAHyQK2Rk3oXChtiAh+jS4WL/GucQon3FH03XoasU8bbMOGjFAOCFhUKGXZxx1FVqkktH9gupkZevIwdbV/B1lnXkvPqN/Ac2YDgXAD9sFDI2EjpkMxmH5kvHthwiDptjKwj0kVKddWyfcLwD1fgBAIAQO4VE0ap4Z8Rq9ofcdOn4rmUrYg2Oum7SOXG85VXa03FXqR9YjeN0AciEer0f7fW6oSGgvVTZ+LDeP2putem+3aNgBgfTbTCmo/u44QfWij4AAMg1gr1TAQCArRZAIQAAd6AQAIAHUAgAwAMoBADgARQCAPAACgEAeACFAAA8gEIAAB5AIQAAD6AQAIAHUAgAwAMoBADgARQCAPAACgEAAAAAAAAAAAAAAAAAAAAAACAkiqL/DyN/4T80/Om0AAAAAElFTkSuQmCC"/>
-
</defs>
-
</svg>
-
<% end %>
-
<% end %>
-
2
</div>
-
<div class="lui-tabs_layout__main">
-
2
<% tabs.each do |tab| %>
-
6
then: 6
else: 0
<%= tab if !tab.disabled %>
-
<% end %>
-
2
then: 0
else: 2
<% if impact.present? %>
-
then: 0
else: 0
<%= impact if !impact.disabled %>
-
<% end %>
-
2
</div>
-
<% end %>
-
1
module LooposUi
-
1
class TabsSection < LoopComponent
-
1
option :icon, optional: true
-
1
option :title, optional: true
-
1
option :description, optional: true
-
1
option :size, default: -> { "normal" }
-
1
option :tooltip, optional: true
-
1
option :no_margin_top, default: -> { false }
-
1
option :underline, default: -> { false }
-
-
1
renders_many :button_groups, "LooposUi::ActionButtons::ButtonGroup"
-
1
renders_many :corner_actions
-
end
-
end
-
then: 0
else: 0
<div class="lui-tabs_section<%= no_margin_top ? "--no-margin" : "" %>">
-
then: 0
else: 0
<% if title || description || icon || tooltip || button_groups.any? || corner_actions.any? %>
-
then: 0
else: 0
<div class="lui-tabs_section__header <%= underline ? "lui-tabs_section__header--underline": ""%>">
-
<%= render LooposUi::TitleDescription.new(icon: icon, title: title, description: description, size: size, tooltip: tooltip ) %>
-
<div class="lui-tabs_section__buttons">
-
<% corner_actions.each do |corner_action| %>
-
<%= corner_action %>
-
<% end %>
-
<% button_groups.each do |bg| %>
-
<%= bg %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
<div class="lui-tabs_section__content">
-
<%= content %>
-
</div>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class TagToken < LooposUi::Token
-
COLORS = {
-
# text, bg
-
1
handling: [find_color("apps-handling-800-primary"), find_color("apps-handling-400")],
-
danger: [find_color("general-danger-800"), find_color("general-danger-400")], # General/Danger/800, General/Danger/400
-
core: [find_color("apps-core-800-primary"), find_color("apps-core-400")], # Apps/Core/800-Primary, Apps/Core/400
-
submission: [find_color("apps-core-800-primary"), find_color("apps-core-400")], # Apps/Submission/800-Primary, Apps/Submission/400
-
impact: [find_color("apps-impact-800-primary"), find_color("apps-impact-400")], # Apps/Impact/800-Primary, Apps/Impact/400
-
manager: [find_color("apps-manager-800-primary"), find_color("apps-manager-400")], # Apps/Manager/800-Primary, Apps/Manager/400
-
hubs: [find_color("apps-hubs-800-primary"), find_color("apps-hubs-400")], # Apps/Hubs/800-Primary, Apps/Hubs/400
-
general: [find_color("general-gray-900"), find_color("general-gray-500")], # , General/Gray/900, General/Gray/500
-
}
-
-
1
def initialize(...)
-
64
super(...)
-
-
64
@leading_icon ||= "fa-regular fa-tag"
-
end
-
-
1
def classes
-
64
"#{super} lui-tag-token"
-
end
-
-
1
def styles
-
64
<<~CSS.squish
-
color: #{@text_color};
-
border-color: #{@bg_color};
-
CSS
-
end
-
-
1
private
-
-
1
def set_color(color)
-
64
then: 32
@text_color, @bg_color = if color.present?
-
32
LooposUi.logger.warn("Manual color should not be set, use for testing only")
-
-
32
then: 32
if COLORS.key?(color.to_sym)
-
32
COLORS[color.to_sym]
-
else: 0
else
-
raise ArgumentError, "Invalid color: #{color}, available colors: #{COLORS.keys.join(", ")}"
-
end
-
else: 32
else
-
32
valid_colors = COLORS.excluding(LooposUi.config.app_type.to_sym, :general).values
-
32
then: 30
if @text.present?
-
30
valid_colors[@text.hash % valid_colors.size]
-
else: 2
else
-
2
valid_colors.sample
-
end
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class ThemeContext < LoopComponent
-
1
class Theme < Dry::Struct
-
# We could set defaults here or in the initializer
-
-
1
attribute? :primary_font, Types::String
-
1
attribute? :primary_color, Types::String
-
1
attribute? :text_color, Types::String
-
-
1
class Button < Dry::Struct
-
# TODO: change the string type to a color type (valid CSS)
-
1
attribute :primary_color, Types::String
-
1
attribute :border_color, Types::String
-
1
attribute :text_color, Types::String
-
1
attribute :hover, Types::Hash.schema(
-
primary_color: Types::String,
-
border_color: Types::String,
-
text_color: Types::String,
-
)
-
end
-
-
1
class TabsLayout < Dry::Struct
-
1
attribute :primary_color, Types::String
-
end
-
-
1
attribute? :button, Button
-
1
attribute? :tabs_layout, TabsLayout
-
-
1
def self.keyfy(key)
-
key.to_s.gsub("_", "-")
-
end
-
-
end
-
-
1
option :theme, Theme
-
1
attr_reader :theme
-
-
1
def call
-
previous_theme_context = LooposUi::Current.theme_context
-
-
LooposUi::Current.theme_context = self
-
-
content_tag(:div, class: "contents lui-theme-context", style: theme_variables) do
-
content
-
end
-
ensure
-
LooposUi::Current.theme_context = previous_theme_context
-
end
-
-
1
private
-
-
1
def theme_variables
-
theme.to_h.flat_map do |key, value|
-
_theme_variable(key, value)
-
end
-
end
-
-
1
def _theme_variable(key, value)
-
key = Theme.keyfy(key)
-
case value
-
when: 0
when Hash
-
value.map do |k, v|
-
_theme_variable("#{key}-#{k}", v)
-
end
-
else: 0
else
-
"--lui-theme-#{key}: #{value};"
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Timeline < LoopComponent
-
1
option :item
-
1
option :is_last
-
end
-
end
-
8
<%= react_component(
-
"Timeline",
-
{
-
item: item,
-
isLast: is_last,
-
}
-
)%>
-
1
module LooposUi
-
-
# TODO: Deprecate this component. Header is the new way to do this.
-
1
class TitleDescription < LoopComponent
-
1
option :icon, optional: true
-
1
option :title, optional: true
-
1
option :description, optional: true
-
1
option :size, Types::Coercible::Symbol.enum(:normal, :small), default: -> { :normal }
-
1
option :tooltip, optional: true
-
1
option :count, optional: true
-
1
option :show_ai_logo, Types::Bool, optional: true, default: -> { false }
-
-
1
renders_one :custom_description
-
1
renders_one :info
-
-
1
def show_ai_logo?
-
@show_ai_logo
-
end
-
end
-
end
-
<% body = capture do %>
-
<span class="lui-title_description__title">
-
then: 0
else: 0
<%= render LooposUi::Icon.new(icon: icon, size:"16") if icon.present? %>
-
<%= title %>
-
then: 0
else: 0
<%= render LooposUi::IconTooltip.new(icon: "fa-regular fa-circle-info", text: tooltip, size: "12") if tooltip.present? %>
-
then: 0
else: 0
<%= render LooposUi::Counter.new(count: count, kind: :neutral, size: :small) if count.present? %>
-
then: 0
else: 0
<%= info if info? %>
-
</span>
-
then: 0
else: 0
<%= tag.span(class: "lui-title_description__description") do %>
-
then: 0
<% if custom_description? %>
-
<%= custom_description %>
-
else: 0
<% else %>
-
<%= description %>
-
<% end %>
-
<% end if custom_description? || description.present? %>
-
<% end %>
-
-
<div class="lui-title_description lui-title_description--<%= size %>">
-
then: 0
<% if show_ai_logo? %>
-
<div class="lui-title_description__row">
-
<%= image_tag("loopos_ai_icon.svg", alt: "LoopOS AI", class: "lui-title_description__ai_logo") %>
-
<div class="lui-title_description__content">
-
<%= body %>
-
</div>
-
</div>
-
else: 0
<% else %>
-
<%= body %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class Toast < LoopComponent
-
1
option :flash
-
1
option :user
-
1
option :type
-
-
1
private
-
-
1
def type_to_icon(type)
-
case type
-
when: 0
when "alert"
-
"fa-light fa-circle-exclamation"
-
when: 0
when "success"
-
"fa-light fa-circle-check"
-
else: 0
else
-
"fa-light fa-circle-xmark"
-
end
-
end
-
end
-
end
-
<div class="flash"
-
data-controller="flash"
-
then: 0
else: 0
then: 0
else: 0
id="flash_container_<%= user&.try(:fetch, "id", nil) || user&.id %>">
-
then: 0
else: 0
<% if flash.present? %>
-
<div data-controller="flash"
-
data-flash-target="content"
-
data-action="click->flash#hide">
-
then: 0
<% if flash.is_a?(Array) %>
-
<% flash.each do |name, msg| %>
-
<%= react_component("GeneralAlert", {
-
title: type.titleize,
-
variant: type,
-
coloredText: false,
-
text: msg,
-
icon: type_to_icon(type),
-
size: "small"
-
}) %>
-
<% end %>
-
else: 0
<% else %>
-
<%= react_component("GeneralAlert", {
-
title: type.titleize,
-
variant: type,
-
coloredText: false,
-
text: flash,
-
icon: type_to_icon(type),
-
size: "small"
-
}) %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
class Toaster < LoopComponent
-
1
include LooposUi::FaviconAware
-
-
1
option :title, Types::Coercible::String
-
1
option :description, Types::Coercible::String, optional: true
-
1
option :variant,
-
Types::Coercible::Symbol.enum(
-
:informative,
-
:success,
-
:warning,
-
:danger,
-
),
-
default: -> { :informative }
-
1
mod :variant
-
-
1
option :is_toast, Types::Bool, default: -> { true }
-
1
mod :is_toast
-
-
1
option :fullwidth, Types::Bool, default: -> { false }
-
1
mod :fullwidth
-
-
1
option :dismissible, Types::Bool, default: -> { true }
-
1
mod :dismissible
-
-
1
option :animated, Types::Bool, default: -> { false }
-
1
mod :animated
-
-
1
TDuration = (Types::Integer | Types::Instance(ActiveSupport::Duration)).constructor do |value|
-
then: 0
if value.is_a?(ActiveSupport::Duration)
-
value.in_seconds * 1000
-
else: 0
else
-
value
-
end
-
end
-
-
1
option :timeout, TDuration, default: -> { 2.seconds }
-
1
option :persistent, Types::Bool, default: -> { false }
-
-
1
class << self
-
1
def variants
-
2
dry_initializer.definitions[:variant].type.values
-
end
-
-
1
def message(*args, **kwargs)
-
new(*args, **kwargs, is_toast: false)
-
end
-
-
1
def toast(*args, **kwargs)
-
new(*args, **kwargs, is_toast: true)
-
end
-
end
-
-
1
variants.each do |variant|
-
4
define_singleton_method(variant) do |**options|
-
new(**options, variant: variant)
-
end
-
end
-
-
1
def icon_tag
-
content_tag(:i, "", class: type_to_icon(variant))
-
end
-
-
1
def dismissible?
-
then: 0
else: 0
is_toast ? dismissible : false
-
end
-
-
1
def animated?
-
then: 0
else: 0
is_toast ? animated : false
-
end
-
-
1
private
-
-
1
def persistent?
-
then: 0
else: 0
is_toast ? persistent : true
-
end
-
-
1
def data
-
{
-
controller: "toaster",
-
toaster_persistent_value: persistent?,
-
toaster_timeout_value: timeout,
-
}
-
end
-
-
1
def type_to_icon(type)
-
case type
-
when: 0
when :informative
-
:info
-
when: 0
when :success
-
:check_circle
-
when: 0
when :warning
-
:error
-
when: 0
when :danger
-
:cancel
-
else
-
skipped
# :nocov:
-
skipped
raise "Invalid variant: #{type}"
-
skipped
# :nocov:
-
end
-
end
-
-
1
def icon_class
-
"lui-toaster__content-title__icon lui-toaster__content-title__icon__#{variant_selector}"
-
end
-
-
1
def variant_selector
-
then: 0
if variant == :danger
-
:error
-
else: 0
else
-
variant
-
end
-
end
-
end
-
-
1
Message = Toaster
-
-
end
-
<%= tag.div(class: classes, data: data) do %>
-
<div class="lui-toaster__header">
-
<%= render LooposUi::Header.new(title: title, description: description, size: :small) do |header| %>
-
<% header.with_semantic_icon(type_to_icon(variant), semantic: variant_selector) %>
-
<% end %>
-
then: 0
else: 0
<%= tag.div(class: "lui-toaster__close", data: { action: "click->toaster#dismiss"}) do %>
-
<%= tag.i(class: "fa-regular fa-xmark fa-xs") %>
-
<% end if dismissible? %>
-
</div>
-
<div class="lui-toaster__content">
-
<%= content %>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
class Toggle < LoopComponent
-
1
option :size, default: -> {:normal}
-
1
option :label, optional: true
-
1
option :position, default: -> {:right}
-
1
option :data_action, optional: true
-
1
option :turbo_frame, optional: true
-
1
option :checked, optional: true
-
1
option :name, optional: true # why wasnt this here before
-
1
option :color, optional: true # deprecated
-
1
option :autoload, default: -> {false}
-
1
option :readonly, default: -> {false}
-
1
option :attrs, Types::Hash, default: -> { {} }
-
-
1
def initialize(**kwargs)
-
@value_passed = kwargs.key?(:value)
-
@value = kwargs.delete(:value)
-
-
super
-
end
-
-
1
private
-
-
1
def attrs
-
deep_merge_args(
-
{
-
class: "flex items-center gap-2 flex-row",
-
data: {
-
controller: "toggle",
-
},
-
},
-
@attrs,
-
)
-
end
-
-
1
def checked?
-
then: 0
if @value_passed
-
@value
-
else: 0
else # legacy usage
-
checked.to_s == "true"
-
end
-
end
-
-
1
def input_value
-
then: 0
else: 0
@value_passed ? @value : "true"
-
end
-
end
-
end
-
<%= tag.div **attrs do %>
-
then: 0
else: 0
<% if label.present? && position == :left %>
-
then: 0
else: 0
<p class="text-general-global-black <%= size == :small ? "copy-10" : "copy-12" %>">
-
<%= label %>
-
</p>
-
<% end %>
-
then: 0
else: 0
<label class="lui-toggle <%= "lui-toggle--small" if size == :small %> relative">
-
<input name="<%=name%>"
-
then: 0
else: 0
<%= readonly ? "disabled='disabled'" : "" %>
-
type="checkbox"
-
value="<%= input_value %>"
-
data-toggle-target="input"
-
then: 0
else: 0
data-action="<%= autoload == true ? "toggle#startLoading" : "" %> <%= data_action %>"
-
data-method="patch"
-
data-turbo_frame="<%= turbo_frame %>"
-
then: 0
else: 0
<%= checked? ? "checked" : "" %>>
-
then: 0
else: 0
<span class="lui-slider" style="<%= 'cursor: not-allowed;' if readonly %>">
-
<span class="lui-toggle__spinner"
-
data-toggle-target="spinner">
-
then: 0
else: 0
<i class="absolute origin-center animate-spin <%= size == :small ? "text-[5px] top-px " : "text-[9px] top-px" %> left-px fa-solid fa-spinner"></i>
-
</span>
-
</span>
-
then: 0
else: 0
<% if readonly %>
-
<div class="lui-toggle__readonly" ></div>
-
<% end %>
-
</label>
-
then: 0
else: 0
<% if label.present? && position == :right %>
-
then: 0
else: 0
<p class="text-general-global-black <%= size == :small ? "copy-10" : "copy-12" %>">
-
<%= label %>
-
</p>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Token < LoopComponent
-
1
include LooposUi::FaviconAware
-
-
COLORS = {
-
# text, bg
-
1
general: [find_color("general-global-black"), nil],
-
}
-
-
1
attr_reader :text
-
1
attr_accessor :draggable
-
-
1
renders_many :actions
-
-
# TODO: migrate to modern option: syntax with dry initialzier
-
1
def initialize(text: nil, color: nil, icon: nil, leading_icon: nil, trailing_icon: nil, locked: false,
-
tooltip: nil, id: nil, disabled: false, kind: :default, key_text: nil, key_value_actions: {}, draggable: false,
-
ellipsis: nil, close: nil, **system_arguments)
-
152
@text = text
-
152
@color = color
-
152
@tooltip = tooltip
-
152
@locked = locked # TODO: Document this
-
152
@id = id
-
152
@disabled = disabled
-
152
@kind = kind
-
152
@key_text = key_text
-
152
@key_value_actions = key_value_actions || {}
-
152
@draggable = draggable
-
152
@ellipsis = ellipsis
-
152
@close = close
-
152
@system_arguments = system_arguments || {}
-
-
152
then: 22
if icon.present?
-
22
@leading_icon = icon
-
else: 130
else
-
130
@leading_icon = leading_icon
-
130
@trailing_icon = trailing_icon
-
end
-
-
152
set_color(color)
-
end
-
-
1
def system_arguments
-
150
@system_arguments.slice(:data)
-
end
-
-
1
def unique_id
-
150
then: 0
@unique_id ||= if @id.present?
-
id
-
else: 150
else
-
150
"lui-token_#{rand(10**10)}"
-
end
-
end
-
-
1
def leading_icon
-
150
then: 86
else: 64
if @leading_icon.present?
-
86
if [
-
"core",
-
"submission",
-
"hubs",
-
"validation",
-
"handling",
-
"manager",
-
"validation-rails",
-
"handling-rails",
-
then: 0
].include?(@leading_icon)
-
else: 86
helpers.app_svg(app: @leading_icon.to_sym, width: 12)
-
86
then: 86
elsif @leading_icon.start_with?("fa-")
-
86
tag.i(class: "lui-token__icon fa-regular #{@leading_icon}")
-
else
-
else: 0
# assume it's a img src
-
tag.img(class: "lui-token__icon-img", src: @leading_icon)
-
end
-
end
-
end
-
-
1
def trailing_icon
-
150
then: 0
else: 150
tag.i(class: "lui-token__icon fa-regular #{@trailing_icon}") if @trailing_icon.present?
-
end
-
-
1
def styles
-
64
then: 0
else: 64
cursor_style = @draggable ? "cursor: grab;" : ""
-
64
then: 0
else: 64
bg_style = @bg_color.present? ? "background-color: #{@bg_color};" : ""
-
-
64
<<~CSS.squish
-
color: #{@text_color};
-
#{bg_style}
-
#{cursor_style}
-
CSS
-
end
-
-
1
def classes
-
[
-
128
then: 0
else: 128
@locked ? "locked" : "",
-
128
then: 0
else: 128
@disabled ? "lui-token--disabled" : "",
-
"lui-entity-token lui-entity-token-general",
-
].compact.join(" ")
-
end
-
-
# This is just a heuristic, tested experimentally. Will change if we change the font size.
-
# TODO: Fix and implement in subclass (should be done in frontend)
-
1
def text_too_long?
-
150
text.present? && text.to_s.length >= 33
-
end
-
-
1
def has_tooltip?
-
150
@tooltip.present? || text_too_long?
-
end
-
-
1
def tooltip_text
-
22
@tooltip.presence || text
-
end
-
-
1
private
-
-
1
def set_color(color)
-
@text_color, @bg_color =
-
64
then: 0
if color.present?
-
then: 0
if COLORS.key?(color.to_sym)
-
else: 0
COLORS[color.to_sym]
-
then: 0
elsif color.to_sym == :app
-
APP_COLORS[LooposUi.config.app_type.to_sym] || COLORS[:general]
-
else: 0
else
-
raise ArgumentError, "Invalid color: #{color}, available colors: #{COLORS.keys.join(", ")}, app."
-
end
-
else: 64
else
-
64
COLORS.excluding(LooposUi.config.app_type.to_sym).values.sample
-
end
-
end
-
end
-
end
-
150
then: 0
<% if @kind == :key_value %>
-
then: 0
else: 0
<%= tag.span(id: unique_id, class: "lui-token #{classes}#{@draggable ? ' draggable' : ''}", style: styles, **system_arguments) do %>
-
then: 0
else: 0
<%= render LooposUi::Tooltip.new(title: tooltip_text) if has_tooltip? %>
-
<%= tag.span("#{@key_text.to_s}: ", class: "lui-token__key-text") %>
-
<%= tag.span(text.presence || "-", class: "lui-token__text", **(@key_value_actions || {})) %>
-
<div class="lui-token__actions">
-
<% actions.each do |action| %>
-
<%= action %>
-
<% end %>
-
then: 0
else: 0
<% if @ellipsis.present? %>
-
<%= tag.button(
-
type: :button,
-
class: "lui-token__action-icon",
-
then: 0
else: 0
**(@ellipsis.is_a?(Hash) ? @ellipsis : {})
-
) do %>
-
<%= render LooposUi::MIcon.new(:more_vert, size: 12) %>
-
<% end %>
-
<% end %>
-
then: 0
else: 0
<% if @close.present? %>
-
<%= tag.button(
-
type: :button,
-
class: "lui-token__action-icon",
-
then: 0
else: 0
**(@close.is_a?(Hash) ? @close : {})
-
) do %>
-
<%= render LooposUi::MIcon.new(:close, size: 12) %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
else: 150
<% else %>
-
300
then: 0
else: 150
<%= tag.span(id: unique_id, class: "lui-token #{classes}#{@draggable ? ' draggable' : ''}", style: styles, **system_arguments) do %>
-
150
then: 22
else: 128
<%= render LooposUi::Tooltip.new(title: tooltip_text) if has_tooltip? %>
-
150
<%= leading_icon %>
-
150
<%= trailing_icon %>
-
150
then: 0
<% if @url.present? %>
-
else: 150
<%= tag.a(text, href: @url, class: "lui-token__text lui-token__text--url", data: { turbo_frame: 'lui-main-layout', turbo_action: :advance }, **@href_options) %>
-
150
then: 148
else: 2
<% elsif text.present? %>
-
148
<%= tag.span(text, class: "lui-token__text") %>
-
<% end %>
-
150
<div class="lui-token__actions">
-
150
<% actions.each do |action| %>
-
<%= action %>
-
<% end %>
-
150
then: 0
else: 150
<% if @ellipsis.present? %>
-
<%= tag.button(
-
type: :button,
-
class: "lui-token__action-icon",
-
then: 0
else: 0
**(@ellipsis.is_a?(Hash) ? @ellipsis : {})
-
) do %>
-
<%= render LooposUi::MIcon.new(:more_vert, size: 12) %>
-
<% end %>
-
<% end %>
-
150
then: 0
else: 150
<% if @close.present? %>
-
<%= tag.button(
-
type: :button,
-
class: "lui-token__action-icon",
-
then: 0
else: 0
**(@close.is_a?(Hash) ? @close : {})
-
) do %>
-
<%= render LooposUi::MIcon.new(:close, size: 12) %>
-
<% end %>
-
<% end %>
-
150
</div>
-
150
then: 0
else: 150
<% if @draggable %>
-
<div class="lui-token__drag-handle">
-
then: 0
else: 0
<%= render LooposUi::Icon.new(icon: "fa-solid fa-grip-dots-vertical", size: "12", color: self.is_a?(LooposUi::EntityToken) ? "#6D6D6D" : "") %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class TokenList < LoopComponent
-
1
EXCESS_TOKENS_LIMIT = 10
-
-
1
renders_many :tokens, types: {
-
tag: LooposUi::TagToken,
-
token: LooposUi::Token,
-
entity: LooposUi::EntityToken,
-
manual: ->(&block) { capture(&block) },
-
}
-
-
1
renders_one :association_button
-
-
1
option :leading_icon, optional: true
-
1
option :max_tokens, optional: true
-
1
option :id, optional: true
-
1
option :direction, optional: true # Optional direction
-
1
option :path, optional: true
-
-
1
def list
-
@list ||= List.new(unique_id: unique_id, max_tokens: max_tokens, path: path)
-
end
-
-
1
class List < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
option :unique_id, Types::String
-
1
option :max_tokens, optional: true
-
1
option :path, optional: true
-
-
1
renders_many :tokens
-
-
1
def turbo_frame_id
-
"#{unique_id}_frame"
-
end
-
-
1
def counter_id
-
"#{unique_id}_counter"
-
end
-
-
1
def out_of_bounds?
-
out_of_bound_token_count.positive?
-
end
-
-
1
def out_of_bound_token_count
-
@out_of_bound_token_count ||= tokens.count - (max_tokens || tokens.count)
-
end
-
-
1
def visible_tokens
-
tokens.first(max_tokens || tokens.count)
-
end
-
-
1
def hidden_tokens
-
tokens.last(out_of_bound_token_count)
-
end
-
-
1
def tooltip_tokens
-
hidden_tokens.first(EXCESS_TOKENS_LIMIT)
-
end
-
-
1
def excess_tooltip_tokens?
-
hidden_tokens.count > EXCESS_TOKENS_LIMIT
-
end
-
end
-
-
1
private
-
-
1
def turbo_frame_id
-
"#{unique_id}_frame"
-
end
-
-
1
def unique_id
-
then: 0
@unique_id ||= if id.present?
-
id
-
else: 0
else
-
"lui-token_list_#{rand(10**10)}"
-
end
-
end
-
-
1
def direction_class
-
then: 0
else: 0
direction == "vertical" ? "lui-token-list--vertical" : "lui-token-list--horizontal"
-
end
-
end
-
end
-
<%= turbo_frame_tag turbo_frame_id, class: "lui-token-list__items" do %>
-
<div data-controller="drag" class="lui-token-list__items">
-
<% visible_tokens.each do |token| %>
-
<%= token %>
-
<% end %>
-
then: 0
else: 0
<% if out_of_bounds? %>
-
<span>
-
<%= render LooposUi::Counter.new(count: out_of_bound_token_count, increment: true) %>
-
<%= render LooposUi::Tooltip.new(interactive: true) do |tooltip| %>
-
<div class="flex flex-col gap-1">
-
<% tooltip_tokens.each do |token| %>
-
<%= token %>
-
<% end %>
-
then: 0
else: 0
<% if excess_tooltip_tokens? %>
-
<span>...</span>
-
then: 0
else: 0
<% if path.present? %>
-
<div class="ml-auto mr-0">
-
<%= render LooposUi::Button.new(text: "View all", kind: :neutral, size: :tiny, type: :tertiary, href: path) do |button| %>
-
<% button.with_counter(count: tokens.count) %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
</span>
-
<% end %>
-
</div>
-
<% end %>
-
<div id="<%= unique_id %>" class="lui-token-list <%= direction_class %>"
-
data-controller="token-list"
-
data-token-list-frame-id-value="<%= turbo_frame_id %>"
-
data-token-list-model-association-overlay-outlet="#<%=unique_id%> .lui-association-overlay">
-
<%= render list do |l| %>
-
<% tokens.each do |token| %>
-
<% l.with_token do %>
-
<%= token %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<%= association_button %>
-
</div>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
class Tooltip < LoopComponent
-
1
VALID_POSITIONS = [
-
"top",
-
"top-start",
-
"top-end",
-
"right",
-
"right-start",
-
"right-end",
-
"bottom",
-
"bottom-start",
-
"bottom-end",
-
"left",
-
"left-start",
-
"left-end",
-
]
-
-
1
renders_one :description # Deprecated, use the content slot instead
-
-
1
renders_many :links, ->(name:, href:) {
-
content_tag(:a, name, href: href)
-
}
-
-
1
renders_many :buttons, LooposUi::Button
-
1
renders_many :tokens, LooposUi::TagToken
-
-
1
option :title, optional: true
-
# Deprecated, avoid using this. You can just nest the tooltip inside the target component
-
1
option :tippy_target_id, optional: true
-
39
option :position, default: proc { "top" }, type: Types::Coercible::String.enum(*VALID_POSITIONS)
-
41
option :interactive, default: proc { false }, type: Types::Params::Bool
-
-
1
private
-
-
1
def description_or_content
-
44
content || description
-
end
-
end
-
end
-
40
<div class="lui-tooltip hidden"
-
data-controller="tooltips"
-
40
data-tooltips-tippy-target-id-value="<%= tippy_target_id %>"
-
40
data-tooltips-position-value="<%= position %>"
-
40
data-tooltips-interactive-value="<%= interactive %>"
-
>
-
40
then: 36
else: 4
<% if title.present? %>
-
36
<div class="lui-tooltip__title">
-
36
<%= title %>
-
</div>
-
<% end %>
-
40
then: 4
else: 36
<% if description_or_content.present? %>
-
4
<div class="lui-tooltip__description">
-
4
<%= description_or_content %>
-
</div>
-
<% end %>
-
40
<% links.each do |link| %>
-
<div class="lui-tooltip__link">
-
<%= link %>
-
</div>
-
<% end %>
-
40
<% tokens.each do |token| %>
-
<%= token %>
-
<% end %>
-
40
<% buttons.each do |button| %>
-
<%= button %>
-
<% end %>
-
40
</div>
-
<%= react_component("Breadcrumbs", { crumbs: breadcrumbs_hash}) %>
-
then: 0
else: 0
<%= turbo_stream_from("#{resource.respond_to?(:id) ? resource.id : resource}_broadcast") %>
-
-
<div class="loopui-form-layout__header">
-
then: 0
else: 0
<div id="breadcrumbs_<%= resource.respond_to?(:id) ? resource.id : resource %>">
-
then: 0
else: 0
<%= render @breadcrumbs_component if @breadcrumbs_hash.present? %>
-
</div>
-
-
<div class="listing__header-right">
-
<% buttons.each do |button| %>
-
<div data-controller="modal">
-
<%= button %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<div class="loopui-form-layout__main-info">
-
<div class="loopui-form-layout__details">
-
<div class="loopui-form-layout__details-section">
-
then: 0
else: 0
<% if @image %>
-
<%= render LooposUi::Image.new(resource: @resource, editable: @image_edit, image_url: @image_url) %>
-
<% end %>
-
<div class="loopui-form-layout__info-section">
-
then: 0
else: 0
<% if top_tags.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
<% top_tags.each do |element| %>
-
<%= element %>
-
<% end %>
-
</div>
-
<% end %>
-
<div class="loopui-form-layout__middle-wrapper--small">
-
then: 0
else: 0
<% if title.present? %>
-
<%= title %>
-
<% end %>
-
then: 0
else: 0
<% if descriptions.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
<% descriptions.each_with_index do |element, index| %>
-
then: 0
else: 0
<% if index > 0 %>
-
<hr class="loopui-form-layout__dot">
-
<% end %>
-
<%= element %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if bottom_tags.present? || under_bottom_tags.present? %>
-
<div class="loopui-form-layout__middle-wrapper">
-
then: 0
else: 0
<% if bottom_tags.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
<% bottom_tags.each_with_index do |element, index| %>
-
then: 0
else: 0
<% if index > 0 %>
-
<hr class="loopui-form-layout__dot">
-
<% end %>
-
<%= element %>
-
<% end %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if under_bottom_tags.present? %>
-
<div class="loopui-form-layout__line-wrapper">
-
<% under_bottom_tags.each_with_index do |element, index| %>
-
then: 0
else: 0
<% if index > 0 %>
-
<hr class="loopui-form-layout__dot">
-
<% end %>
-
<%= element %>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if right_side.present? %>
-
<div class="loopui-form-layout__right">
-
<%= right_side %>
-
</div>
-
<% end %>
-
</div>
-
then: 0
else: 0
<% if card.present? %>
-
<%= card %>
-
<% end %>
-
</div>
-
</div>
-
<div class="loopui-usearch__advanced-search">
-
then: 0
else: 0
<% if header.present? %>
-
<div class="loopui-usearch__advanced-search__header">
-
<%= header %>
-
</div>
-
<% end %>
-
<div class="loopui-usearch__advanced-search__filters">
-
<% filters.each_with_index do |element, index| %>
-
<%= element %>
-
<% end %>
-
then: 0
else: 0
<% if date_filter.present? %>
-
<div>
-
<%= date_filter %>
-
<%= react_component("DatetimePicker", { kind: "DateTimePicker", lang: I18n.locale, variant: @variant, isRange: true, granularity: "time", placeholder: I18n.t('global_search.select_date') }) %>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module UniversalSearchComponent
-
1
class UniversalSearchFilters < ViewComponent::Base
-
1
renders_one :header
-
1
renders_many :filters
-
1
renders_one :date_filter
-
-
1
def initialize(variant:)
-
@variant = variant
-
end
-
end
-
end
-
end
-
<div class="loopui-usearch__footer">
-
<div class="loopui-usearch__footer--selected-filters">
-
<span class="loopui-usearch__footer--label"><%= I18n.t('global_search.filters_selected') %> <span id="filters-counter"></span></span>
-
then: 0
else: 0
<% if selected_filters.present? %>
-
<%= selected_filters %>
-
<% end %>
-
-
</div>
-
then: 0
else: 0
<% if clear_button.present? %>
-
<%= clear_button %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module UniversalSearchComponent
-
1
class UniversalSearchFooter < ViewComponent::Base
-
1
renders_one :selected_filters
-
1
renders_one :clear_button
-
-
1
def initialize()
-
end
-
end
-
end
-
end
-
<div class="loopui-usearch__global-search">
-
then: 0
else: 0
<% if search_box.present? %>
-
<%= search_box %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module UniversalSearchComponent
-
1
class UniversalSearchGlobalSearch < ViewComponent::Base
-
1
renders_one :search_box
-
-
1
def initialize()
-
end
-
end
-
end
-
end
-
then: 0
else: 0
<div id="results_container" class="loopui-usearch__tabs-results--container<%= @version.present? ? "_#{@version}" : "" %> hidden">
-
then: 0
else: 0
<div class="loopui-usearch__tabs<%= @version.present? ? "_#{@version}" : "" %>">
-
<% tabs.each_with_index do |tab, index| %>
-
<%= tab %>
-
<% end %>
-
</div>
-
<div class="loopui-usearch__results overflow-results">
-
<% tables.each_with_index do |table, index| %>
-
<%= table %>
-
<% end %>
-
</div>
-
</div>
-
<div id="global-search-no-search" class="flex items-center justify-center min-h-[200px]">
-
<p><%= I18n.t('global_search.fill_search_fields') %></p>
-
</div>
-
<div id="empty-results-all-tables" class="hidden">
-
<div class="flex flex-1 items-center justify-center absolute w-full" style="top: 200px">
-
<p><%= I18n.t('global_search.no_results') %></p>
-
</div>
-
</div>
-
<div id="loading_spinner" role="status" class="hidden absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
-
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
-
<span class="sr-only">Loading...</span>
-
</div>
-
1
module LooposUi
-
1
module UniversalSearchComponent
-
1
class UniversalSearchResults < ViewComponent::Base
-
1
renders_many :tabs
-
1
renders_many :tables
-
-
1
def initialize(version: "")
-
@version = version
-
end
-
-
# Backward compatibility with ViewComponent v2
-
# Remove after updating universal_search usages of this component
-
# app/views/loopos_universal_search/_algolia_search.html.erb
-
1
alias_method :tab, :with_tab
-
1
alias_method :table, :with_table
-
end
-
end
-
end
-
1
module LooposUi
-
1
class UserMenu < LoopComponent
-
1
option :context, Types::Symbol.enum(:sidebar, :user_menu)
-
1
mod :context, condition: -> { "in_#{context}" }
-
-
1
option :collapsed, Types::Bool, default: -> { false }
-
1
mod :collapsed
-
-
1
TUrl = (Types::String | Types::Symbol.enum(:default)).constructor do |v|
-
valid = begin
-
uri = URI.parse(v)
-
uri.is_a?(URI::HTTP) && uri.host.present?
-
rescue URI::InvalidURIError
-
false
-
end
-
-
then: 0
else: 0
valid ? v : :default
-
end
-
-
1
option :name, Types::String
-
1
option :user_image_url, TUrl
-
1
option :partner_name, Types::String
-
1
option :partner_image_url, TUrl
-
-
1
class << self
-
1
def user_menu(*args, **kwargs, &block)
-
-
new(*args, **kwargs, context: :user_menu, &block)
-
end
-
-
1
def sidebar(*args, **kwargs, &block)
-
new(*args, **kwargs, context: :sidebar, &block)
-
end
-
end
-
-
1
def initialize(*args, **kwargs)
-
# TODO: Maybe check if the user tried to pass a name or something
-
# for now, it will be loaded from the sidebar singleton
-
config = ::LooposUi.config.sidebar
-
config_kwargs = {
-
name: config.foo["user"]["full_name"],
-
user_image_url: config.foo["user"]["avatar_url"],
-
partner_name: config.foo["user"]["partner"]["name"],
-
partner_image_url: config.foo["user"]["partner"]["icon_url"],
-
}
-
kwargs.merge!(config_kwargs)
-
-
super(*args, **kwargs)
-
end
-
-
1
def groups
-
::LooposUi.config.sidebar.foo["navigation"]["groups"].map do |group|
-
group.with_indifferent_access
-
end
-
end
-
-
1
def logout_path
-
path = ::LooposUi.config.sidebar.logout_path
-
case path
-
when: 0
when String
-
path
-
when: 0
when Proc
-
instance_exec(&path)
-
else: 0
else
-
raise "Invalid logout_path: #{path.inspect}"
-
end
-
end
-
end
-
end
-
then: 0
<% if context == :sidebar %>
-
<%= render LooposUi::Popover.new(anchor: :bottom_left, position: :bottom_right) do |pop| %>
-
<% pop.with_custom_toggle do %>
-
<div class="<%= classes %>">
-
<%= tag.div(class: "lui-user_menu__avatar") do %>
-
<%= render LooposUi::GroupAvatar.new(main_avatar: user_image_url, secondary_avatar: partner_image_url) %>
-
<% end %>
-
<%= tag.div(class: "lui-user_menu__names") do %>
-
<%= tag.div(class: "lui-user_menu__name") do %>
-
<%= name %>
-
<% end %>
-
<%= tag.div(class: "lui-user_menu__partner") do %>
-
<%= partner_name %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end%>
-
<% pop.with_target do %>
-
<div class="lui-user_menu__dropdown">
-
<%= render LooposUi::UserMenu.user_menu %>
-
</div>
-
<% end%>
-
<% end %>
-
<% else %>
-
else: 0
<%# move these to css file %>
-
<div class="flex flex-col items-stretch min-w-[250px] rounded-lg border border-(--color-border-primary) bg-(--color-background)">
-
<div class="flex flex-col p-4 gap-4 items-stretch">
-
<div class="<%= classes %>">
-
<%= tag.div(class: "lui-user_menu__avatar") do %>
-
<%= render LooposUi::Avatar.user(image_url: user_image_url, size: :xl) %>
-
<% end %>
-
<%= tag.div(class: "lui-user_menu__names") do %>
-
<%= tag.div(class: "lui-user_menu__name") do %>
-
<%= name %>
-
<% end %>
-
<%= tag.div(class: "lui-user_menu__partner") do %>
-
<%= render LooposUi::Avatar.partner(image_url: partner_image_url, size: :xs) %>
-
<%= partner_name %>
-
<% end %>
-
<% end %>
-
</div>
-
<div class="flex flex-col gap-1">
-
<%# raise groups.inspect%>
-
<% groups.each do |group| %>
-
<div class="flex flex-col gap-2">
-
<div class="group flex flex-col">
-
<%= tag.span(group[:title], class: "copy-12-medium text-(--color-content-secondary)") %>
-
<% group[:items].each do |item| %>
-
then: 0
<% if item[:disabled] %>
-
<span class="lui-user_menu__nav_link">
-
<%= tag.span(class: "text-(--color-content-secondary) cursor-not-allowed") do %>
-
<%= item[:title] %>
-
then: 0
else: 0
<%= render LooposUi::Tooltip.new(title: item[:tooltip]) if item[:tooltip] %>
-
<% end %>
-
</span>
-
else: 0
<% else %>
-
<%= link_to item[:path], class: "lui-user_menu__nav_link", data: { turbo_frame: "lui-main-layout", turbo_action: "advance" } do %>
-
<%= tag.span do %>
-
<%= item[:title] %>
-
then: 0
else: 0
<%= render LooposUi::Tooltip.new(title: item[:tooltip]) if item[:tooltip] %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
</div>
-
</div>
-
<% end %>
-
</div>
-
</div>
-
<footer class="flex px-4 py-2 bg-(--color-secondary) justify-between items-center">
-
<div class="flex flex-col gap-[2px]">
-
<span class="copy-12-medium text-(--color-content-secondary)">
-
Powered by ...
-
</span>
-
<span class="copy-12-medium text-(--color-content-secondary)">
-
Version 1.0.0
-
</span>
-
</div>
-
<%= render LooposUi::Button.new(
-
text: "Log out",
-
type: :tertiary,
-
size: :small,
-
kind: :danger,
-
href: logout_path,
-
tag_options: { data: { turbo_method: :delete } }
-
) %>
-
</footer>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class AppLayout < LoopComponent
-
1
include Turbo::FramesHelper
-
1
include Turbo::StreamsHelper
-
1
include LooposUi::StreamToasters
-
-
1
option :stream_namespaces,
-
default: -> { [] },
-
1
type: (Types::Coercible::Symbol | Types::Array.of(Types::Coercible::Symbol)).constructor { |value|
-
then: 0
else: 0
value.is_a?(Symbol) ? [value] : value
-
}
-
-
1
renders_one :navbar
-
1
renders_one :sidebar, ->(**kwargs) { LooposUi::Sidebar.new(**kwargs) }
-
1
renders_one :bottom_bar
-
1
renders_one :action_panel
-
1
renders_one :action_bar
-
1
renders_one :select_bar
-
-
1
def before_render
-
else: 0
then: 0
with_sidebar unless sidebar? || @without_sidebar
-
end
-
-
1
def without_sidebar
-
@without_sidebar = true
-
end
-
-
1
def toasters_stream_target_key
-
toasters_stream_target(keys: stream_namespaces)
-
end
-
-
1
class RootStyles < LoopComponent
-
end
-
end
-
end
-
end
-
<turbo-frame id="lui-app-layout" data-turbo-action="advance" class="lui-v2-app-layout" data-controller="miniapps lui--layout">
-
<%= render LooposUi::V2::AppLayout::RootStyles.new %>
-
<div class="w-full lui-header-slot">
-
<%= navbar %>
-
</div>
-
<div class="lui-v2-app-layout__content">
-
<%= sidebar %>
-
<div class="lui-v2-app-layout__content-container lui-app-layout_id">
-
<turbo-frame
-
data-turbo-action="advance"
-
data-turbo-frame="lui-main-layout"
-
id="lui-main-layout"
-
class="lui-v2-app-layout__content-inner"
-
style="width: 100%; transition: width 25ms ease-in;"
-
>
-
<%= select_bar %>
-
<div id="lui-action-bar">
-
then: 0
else: 0
<%= action_bar if action_bar.present? && action_bar.to_s.strip.present? %>
-
</div>
-
<div class="lui-action_bar lui-action_bar--loading">
-
<div class="lui-action_bar__left w-1/4">
-
<div class="lui-skeleton__bar"></div>
-
</div>
-
<div class="lui-action_bar__right w-1/4">
-
<div class="lui-skeleton__bar"></div>
-
</div>
-
</div>
-
<div class="flex-1 flex min-h-0 overflow-hidden">
-
<%
-
# I want to cal active_item but internaly it calls helpers (path etc) but I dont want to render the component just for that
-
# So we pass in the current view context manually
-
then: 0
else: 0
if LooposUi::Sidebar::USE_UI_2
-
sd = LooposUi::Sidebar::V2::Sidebar.new
-
sd.instance_variable_set(:@view_context, view_context)
-
end
-
%>
-
then: 0
else: 0
<%= helpers.turbo_stream.update("lui-sidebar-active-item-id", sd.active_item) if LooposUi::Sidebar::USE_UI_2 %>
-
<%= render LooposUi::LayoutLoading.new %>
-
<%= tag.div(
-
class: "lui-main-layout__container",
-
data: {
-
skeleton_loading: LooposUi.config.enable_loading_skeletons
-
}
-
) do %>
-
<%= tag.div class: "lui-main-layout__content", id: "lui-main-layout-content" do %>
-
<%= content %>
-
<% end %>
-
then: 0
else: 0
<%= tag.div class: "lui-main-layout__action-panel", data: { "lui--layout-target": "actionPanel" }, id: "lui-action-panel" do %>
-
<%= tag.div class: "lui-action-panel-resizer",
-
data: {
-
action: "pointerdown->lui--layout#handleDragStart"
-
} %>
-
<%= action_panel %>
-
<% end if action_panel.present? && action_panel.to_s.strip.present? %>
-
<% end %>
-
</div>
-
then: 0
else: 0
<%= tag.div class: "lui-v2-app-layout__bottom-bar" do %>
-
<%= bottom_bar %>
-
<% end if bottom_bar.present? %>
-
</turbo-frame>
-
</div>
-
</div>
-
<%= turbo_stream_from "lui-app-layout" %>
-
<%= turbo_stream_from toasters_stream_target_key %>
-
<%= tag.div(
-
id: "lui-toasters",
-
popover: "manual",
-
data: {
-
controller: "toasters",
-
toasters_new_toaster_url_value: LooposUi::Engine.routes.url_helpers.toasters_path
-
}
-
) %>
-
</turbo-frame>
-
<style>
-
:root {
-
--app-900-hover: <%= LooposUi::Colors.find("apps-900-hover") %>;
-
--app-800-primary: <%= LooposUi::Colors.find("apps-800-primary") %>;
-
--app-400: <%= LooposUi::Colors.find("apps-400") %>;
-
--app-300: <%= LooposUi::Colors.find("apps-300") %>;
-
--app-200: <%= LooposUi::Colors.find("apps-200") %>;
-
--app-100: <%= LooposUi::Colors.find("apps-100") %>;
-
--app-opacity5: <%= LooposUi::Colors.find("apps-opacity5") %>;
-
-
/* Current app variables, for semantic colors */
-
--current-surface-app: var(--color-surface-apps-<%= LooposUi.config.app_type %>);
-
--current-text-app: var(--color-text-apps-<%= LooposUi.config.app_type %>);
-
}
-
</style>
-
1
module LooposUi
-
1
module V2
-
1
class Card < LoopComponent
-
1
option :title, Types::String, optional: true
-
1
option :description, Types::String, optional: true
-
1
option :logo, Types::String, optional: true
-
-
# Modifiers
-
1
option :draft, Types::Bool, optional: true, default: -> { false }
-
1
mod :draft
-
-
1
option :full, Types::Bool, optional: true, default: -> { false }
-
1
mod :full
-
-
1
option :borderless, Types::Bool, optional: true, default: -> { false }
-
1
mod :borderless
-
-
1
option :footer_button_args, Types::Hash, optional: true, default: -> { {} }
-
-
1
renders_one :title_description, ->(*args, **kwargs) { LooposUi::TitleDescription.new(*args, **kwargs, size: :small) }
-
1
renders_one :custom_description
-
1
renders_one :footer
-
1
renders_one :corner
-
1
renders_one :logo_img
-
-
# Include this module in your card to make it lazy loadable
-
# it will add src and id options. Specifying src makes the card lazy loadable.
-
1
module LazyLoading
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
8
option :id, Types::Coercible::String, optional: true
-
8
option :src, Types::String, optional: true
-
end
-
-
1
def lazy_loading_options
-
{ id: @id, src: @src }
-
end
-
-
1
def turbo_options
-
lazy_loading_options.slice(:src)
-
end
-
-
1
def initialize(...)
-
super(...)
-
then: 0
else: 0
if turbo_options[:lazy] && (turbo_options[:src].blank? || @id.blank?)
-
raise ArgumentError, "You must provide a `src` and an `id` if you want to use lazy loading"
-
end
-
end
-
end
-
-
1
include LazyLoading
-
-
1
def before_render
-
else: 0
then: 0
with_title_description(title: title, description: description) do |td|
-
then: 0
else: 0
td.with_custom_description_content(custom_description) if custom_description
-
end unless title_description || empty_title_description_slot?
-
end
-
-
1
def empty_title_description_slot?
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
title.blank? && title_description&.title.blank? && title_description&.description.blank? && title_description&.custom_description.blank?
-
end
-
-
1
def footer_button_args
-
{
-
size: :small, type: :secondary,
-
}
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Card.new(title: app_instance.title, **lazy_loading_options) do |card| %>
-
<% card.with_custom_description do %>
-
<%= render LooposUi::Entities::Partnable.new(partnable: app_instance.partner) %>
-
<% end %>
-
<div class="lui-app_card-content">
-
<div class="lui-app_card-content-info">
-
<span class="lui-app_card-content-info__title">
-
<%= LooposUi::V2::Card.t(".app_instance.release") %>
-
</span>
-
<span class="lui-app_card-content-info__text">
-
<%= app_instance.release %>
-
</span>
-
</div>
-
<div class="lui-app_card-content-info">
-
<span class="lui-app_card-content-info__title">
-
<%= LooposUi::V2::Card.t(".app_instance.id") %>
-
</span>
-
<span class="lui-app_card-content-info__text">
-
<%= app_instance.id %>
-
</span>
-
</div>
-
</div>
-
-
<% card.with_logo_img do %>
-
<div style="width: 108px; height: 108px; overflow: hidden; position: relative;">
-
<div style="position: absolute; bottom: -20px; right: -20px;">
-
<%= helpers.app_svg(app: app_instance.app, size: :huge) %>
-
</div>
-
</div>
-
<% end %>
-
<% card.with_corner do %>
-
<%= render LooposUi::StateLabel.new(text: app_instance.status, icon: tag_status.dig(:icon), color: tag_status.dig(:kind)) %>
-
<% end %>
-
<% card.with_footer do %>
-
<%= render LooposUi::ActionButtons.new do |ab| %>
-
<% ab.with_button_group do |bg| %>
-
<% bg.with_button(
-
text: LooposUi::V2::Card.t(".app_instance.details"),
-
tooltip_text: LooposUi::V2::Card.t(".app_instance.details_tooltip"),
-
icon: "fa-sharp fa-regular fa-arrow-up-right-from-square",
-
href: details_href,
-
**(card.footer_button_args.merge(footer_button_args.presence || {}))
-
) %>
-
<% bg.with_button(
-
text: LooposUi::V2::Card.t(".app_instance.open"),
-
tooltip_text: LooposUi::V2::Card.t(".app_instance.open_tooltip", app: app_instance.app.capitalize),
-
icon: "fa-sharp fa-regular fa-arrow-up-right-from-square",
-
href: open_href,
-
**(card.footer_button_args.merge(footer_button_args.presence || {}).merge({ tag_options: { target: :_blank } }))) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class AppInstance < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class AppInstance < Dry::Struct
-
1
attribute :object, Types.Instance(Object)
-
-
1
attribute :title, Types::String
-
1
attribute :partner, Types::Any
-
1
attribute :app, Types::String
-
1
attribute :status, Types::String
-
1
attribute :release, Types::String
-
1
attribute :id, Types::String
-
-
1
def self.build(app_instance)
-
new(
-
object: app_instance,
-
title: app_instance.name,
-
partner: app_instance.partner,
-
app: app_instance.app,
-
status: app_instance.status,
-
release: app_instance.app_release,
-
id: app_instance.id,
-
)
-
end
-
end
-
-
1
option :app_instance, Types.Instance(AppInstance).constructor(AppInstance.method(:build))
-
1
option :open_href, Types::String
-
1
option :details_href, Types::String
-
1
option :footer_button_args, Types::Hash, optional: true, default: -> { {} }
-
-
1
def tag_status
-
case app_instance.status
-
when: 0
when "creating", "updating", "deleting", "stopped"
-
{
-
kind: "notice",
-
icon: "fa-regular fa-circle-exclamation",
-
}
-
when: 0
when "created", "to_create", "running"
-
{
-
kind: "success",
-
icon: "fa-regular fa-check-circle",
-
}
-
when: 0
when "error", "error_creating", "error_running", "error_updating", "error_deleting", "to_delete", "deleted", "partially_deleted"
-
{
-
kind: "danger",
-
icon: "fa-regular fa-circle-xmark",
-
}
-
else: 0
else
-
{
-
kind: "informative",
-
icon: "fa-regular fa-circle",
-
}
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= tag.turbo_frame id: (id || "card-#{rand(10**10).to_s(16)}"), class: classes, **turbo_options do %>
-
then: 0
else: 0
<%= tag.div(class: "lui-card_v2-top") do %>
-
then: 0
else: 0
<%= title_description if title_description.present? %>
-
then: 0
else: 0
<%= tag.div(class: "lui-card_v2-corner") do %>
-
<%= corner %>
-
<% end if corner.present? %>
-
<% end if title_description.present? || corner.present? %>
-
<%# For some reason. content? returns true even tho it appears to be empty %>
-
then: 0
else: 0
<%= tag.div(content, class: "w-full") if content? && content.present? %>
-
then: 0
else: 0
<% if footer.present? %>
-
<div class="w-full">
-
<%= footer %>
-
</div>
-
<%end%>
-
then: 0
<% if logo_img.present? %>
-
<div class="lui-card_v2-img">
-
<%= logo_img %>
-
else: 0
</div>
-
then: 0
else: 0
<%elsif logo.present? %>
-
<div class="lui-card_v2-img">
-
<>
-
<img src=<%=logo%> alt="Logo" />
-
</div>
-
</div>
-
<% end %>
-
<% end %>
-
<%= render LooposUi::V2::Card.new(full: true, **lazy_loading_options) do |card| %>
-
<% card.with_title_description(title: dashboard_info.title, description: dashboard_info.description, tooltip: dashboard_info.tooltip, size: "small") %>
-
then: 0
else: 0
<% card.with_corner do %>
-
<%= select_form %>
-
<% end if select_form.present? %>
-
then: 0
else: 0
<%= content if content? %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class Dashboard < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class Dashboard < Dry::Struct
-
1
attribute :object, Types.Instance(Object)
-
-
1
attribute :title, Types::String
-
1
attribute :description, Types::String
-
1
attribute :tooltip, Types::String
-
-
1
def self.build(dashboard_info)
-
new(
-
object: dashboard_info,
-
title: dashboard_info.title,
-
description: dashboard_info.description,
-
tooltip: dashboard_info.tooltip,
-
)
-
end
-
end
-
-
1
option :dashboard_info, Types.Instance(Dashboard).constructor(Dashboard.method(:build))
-
-
1
renders_one :select_form
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Card.new(draft: true, **lazy_loading_options) do |card| %>
-
<% card.with_title_description(title: draft_info.title, description: draft_info.description, size: "small") %>
-
then: 0
else: 0
<%= content if content? %>
-
<% card.with_corner_content(corner) %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class Draft < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class Draft < Dry::Struct
-
1
attribute :object, Types.Instance(Object)
-
-
1
attribute :title, Types::String
-
1
attribute :description, Types::String
-
-
1
def self.build(draft_info)
-
new(
-
object: draft_info,
-
title: draft_info.title,
-
description: draft_info.description,
-
)
-
end
-
end
-
1
option :draft_info, Types.Instance(Draft).constructor(Draft.method(:build))
-
-
1
renders_one :corner
-
end
-
end
-
end
-
end
-
<div data-controller="collapsiblev2">
-
<%= render LooposUi::V2::Card.new(full: true, **lazy_loading_options) do |card| %>
-
<% card.with_title_description(title: "Favorites", icon: "fa-solid fa-star", size: "small") %>
-
<% card.with_footer do %>
-
<div class="w-full flex justify-end">
-
<%= render LooposUi::Button.new(
-
text: "View more",
-
size: :tiny,
-
type: :secondary,
-
trailing_icon: "fa-regular fa-chevron-down",
-
tag_options: { data: {"collapsiblev2-target": "button", "action": "click->collapsiblev2#toggle"}}
-
) %>
-
</div>
-
<% end %>
-
then: 0
else: 0
<% if content.present? %>
-
<div data-collapsiblev2-target="container">
-
<%= content%>
-
</div>
-
<% end %>
-
<% end %>
-
</div>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class Favorites < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Card.new(draft: draft, **lazy_loading_options) do |card| %>
-
<% card.with_title_description(title: financial_info.title, description: financial_info.description, count: financial_info.count) do |title_description| %>
-
then: 0
else: 0
<% title_description.with_info do %>
-
<%= title_info %>
-
<% end if title_info.present? %>
-
<% end %>
-
<% card.with_corner do %>
-
<div class="flex items-center gap-2">
-
then: 0
else: 0
<%= safe_join(corner_actions) if corner_actions.present? %>
-
<%= modal %>
-
</div>
-
<% end %>
-
<div>
-
<div class="lui-financial_card-content-info">
-
<span class="lui-financial_card-content-info__amount">
-
then: 0
<% if financial_info.amount.nil? || financial_info.amount.zero? %>
-
-
-
else: 0
<% else %>
-
<%= financial_info.amount.format(symbol_position: :after) %>
-
<% end %>
-
</span>
-
then: 0
else: 0
<%= render LooposUi::StateLabel.new(text: financial_info.status.capitalize, color: tag_status.dig(:kind)) if financial_info.status.present? %>
-
then: 0
else: 0
<%= render LooposUi::EntityToken.new(text: "FTI") if fti? %>
-
</div>
-
<span class="lui-financial_card-content-info__timestamp">
-
then: 0
else: 0
<%= render LooposUi::DateShow.new(date: financial_info.timestamp, format: :short) if financial_info.timestamp.present? %>
-
</span>
-
</div>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class FinancialLog < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class FinancialLog < Dry::Struct
-
1
attribute :header_info, Types.Instance(Object)
-
1
attribute :item_value, Types.Instance(Object)
-
-
1
attribute :title, Types::String
-
1
attribute :description, Types::String
-
1
attribute :count, Types::String.optional
-
-
1
attribute :amount, Types.Instance(Money).optional
-
1
attribute :status, Types::String.optional
-
1
attribute :timestamp, Types::Date.optional
-
-
1
def self.build(header_info, item_value)
-
new(
-
header_info: header_info,
-
item_value: item_value,
-
title: header_info.title,
-
description: header_info.description,
-
then: 0
else: 0
count: header_info.try(:count)&.to_s,
-
amount: item_value.amount || item_value.amount_cents.to_d / 100,
-
status: item_value.try(:status),
-
then: 0
else: 0
timestamp: item_value.try(:updated_at)&.to_date,
-
)
-
end
-
end
-
-
1
option :header_info, Types.Instance(Object)
-
1
option :item_value, Types.Instance(Object)
-
1
option :fti, Types::Bool, default: -> { false }
-
1
alias_method :fti?, :fti
-
1
option :draft, Types::Bool, default: -> { false }
-
-
1
renders_one :title_info
-
-
1
renders_many :corner_actions, ->(&block) { capture(&block) }
-
-
1
renders_one :modal, ->(trigger_args: {}, show_footer: false, **args) {
-
modal = Modal.new(show_footer: show_footer, **args)
-
modal.with_header(title: financial_info.title, description: financial_info.description) do |header|
-
then: 0
else: 0
header.with_info do
-
title_info.to_s
-
end if title_info.present?
-
end
-
-
# Default trigger button
-
modal.with_trigger_button(
-
icon: "fa-regular fa-arrow-up-right-and-arrow-down-left-from-center",
-
tooltip_text: I18n.t("admin.items.financial_data.manual_item_value.open_modal"),
-
size: :small,
-
kind: :neutral,
-
type: :secondary,
-
**trigger_args,
-
)
-
modal
-
}
-
-
1
def financial_info
-
@financial_info ||= FinancialLog.build(header_info, item_value)
-
end
-
-
1
def tag_status
-
status = financial_info.status.to_s.downcase
-
case status
-
when: 0
when "agreed", "transacted"
-
{
-
kind: "success",
-
}
-
when: 0
when "waiting_agreement", "draft"
-
{
-
kind: "warning",
-
}
-
else: 0
else
-
{
-
kind: "neutral",
-
}
-
end
-
end
-
end
-
end
-
end
-
end
-
<%
-
transaction = financial_transaction.transaction
-
%>
-
<%= render LooposUi::V2::Card.new(
-
then: 0
else: 0
title: financial_transaction.type == :incoming ? I18n.t("admin.payments.financial_transaction.incoming_title") : I18n.t("admin.payments.financial_transaction.outgoing_title"),
-
**lazy_loading_options
-
) do |card| %>
-
<% card.with_corner do %>
-
then: 0
else: 0
<% if transaction.provider.present? %>
-
<%= render LooposUi::Entities::Provider.new(provider: financial_transaction.transaction.provider, tooltip: I18n.t("admin.payments.financial_transaction.provider_tooltip")) %>
-
<% end %>
-
<% end %>
-
<% card.with_custom_description do %>
-
then: 0
<% if transaction.show_url.present? %>
-
<% link_to transaction.show_url, target: :_blank do %>
-
<%= transaction.id %>
-
<% end %>
-
else: 0
<% else %>
-
<%= transaction.id %>
-
<% end %>
-
<% end %>
-
<div>
-
<div class="lui-financial_card-content-info">
-
<span class="lui-financial_card-content-info__amount">
-
<%= transaction.amount %>
-
</span>
-
then: 0
else: 0
<%= render LooposUi::StateLabel.new(text: transaction.status.capitalize, color: tag_status.dig(:kind)) if transaction.status.present? %>
-
</div>
-
<span class="lui-financial_card-content-info__timestamp">
-
then: 0
else: 0
<%= render LooposUi::DateShow.new(date: transaction.paid_date, format: :long) if transaction.paid_date.present? %>
-
</span>
-
</div>
-
then: 0
else: 0
<% card.with_footer do %>
-
<% financial_transaction.barcodes_html.each do |barcode_value, barcode_html| %>
-
<div class="lui-financial_transaction-content-barcode">
-
<%= raw barcode_html %>
-
</div>
-
<div class="lui-financial_transaction-content-barcode-value">
-
<%= barcode_value %>
-
</div>
-
<% end %>
-
<% end if financial_transaction.barcodes_html.present? %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class FinancialTransaction < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class FinancialTransaction < Dry::Struct
-
1
attribute :incoming_payment, Types.Instance(LooposUi::Resources::IncomingPaymentResource::IncomingPayment).optional.default(nil)
-
1
attribute :outgoing_payment, Types.Instance(LooposUi::Resources::PaymentResource::Payment).optional.default(nil)
-
-
1
class << self
-
1
def build(incoming_payment: nil, outgoing_payment: nil)
-
then: 0
if incoming_payment.present?
-
else: 0
new(incoming_payment: incoming_payment)
-
then: 0
elsif outgoing_payment.present?
-
new(outgoing_payment: outgoing_payment)
-
else: 0
else
-
raise ArgumentError, "Either incoming_payment or outgoing_payment must be present"
-
end
-
end
-
end
-
-
1
def transaction
-
incoming_payment.presence || outgoing_payment.presence
-
end
-
-
1
def type
-
then: 0
if incoming_payment.present?
-
else: 0
:incoming
-
then: 0
else: 0
elsif outgoing_payment.present?
-
:outgoing
-
end
-
end
-
-
1
def barcodes_html
-
then: 0
else: 0
then: 0
else: 0
barcode_values = transaction.extra_data&.with_indifferent_access&.dig("barcode_codes")
-
then: 0
else: 0
then: 0
else: 0
barcode_format = transaction.extra_data&.with_indifferent_access&.dig("barcode_format") || "code_128"
-
then: 0
else: 0
return if barcode_values.blank? || barcode_format.blank?
-
-
barcode_values = [barcode_values].flatten.compact
-
possible_class = "Barby::#{barcode_format.camelize}".safe_constantize
-
-
then: 0
else: 0
if possible_class.blank?
-
possible_class = "Barby::#{barcode_format.camelize}A".safe_constantize
-
end
-
-
# In case the barcode format is not supported, use the default code 128A
-
possible_class ||= "Barby::Code128A".safe_constantize
-
barcode_values.map do |barcode_value|
-
barcode = possible_class.new(barcode_value)
-
outputter = Barby::HtmlOutputter.new(barcode)
-
[barcode_value, outputter.to_html(class_name: "lui-financial_transaction-content-barcode")]
-
end
-
end
-
end
-
-
1
option :financial_transaction, Types.Instance(FinancialTransaction)
-
-
1
renders_one :title_info
-
-
1
def tag_status
-
status = financial_transaction.transaction.status.to_s.downcase
-
case status
-
when: 0
when "completed", "transacted"
-
{
-
kind: "success",
-
}
-
when: 0
when "waiting_agreement", "draft"
-
{
-
kind: "warning",
-
}
-
else: 0
else
-
{
-
kind: "neutral",
-
}
-
end
-
end
-
end
-
end
-
end
-
end
-
<%= render LooposUi::V2::Card.new(title: script.name, description: description, **lazy_loading_options) do |card| %>
-
<% card.with_corner do %>
-
<%= modal %>
-
<% end %>
-
<% card.with_footer do %>
-
then: 0
else: 0
<% if script.last_run_result_file.present? %>
-
<% render LooposUi::Button.new(
-
icon: "fa-regular fa-file-arrow-down",
-
tooltip_text: LooposUi::V2::Card.t(".script.download_result"),
-
text: script.last_run_result_file_name,
-
kind: :neutral,
-
type: :tertiary,
-
href: script.last_run_result_file,
-
) %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class Card
-
1
class Script < LoopComponent
-
1
include LooposUi::V2::Card::LazyLoading
-
-
1
class Script < Dry::Struct
-
1
attribute :object, Types.Instance(Object)
-
1
attribute :name, Types::String
-
1
attribute :last_run_result_file, Types::String.optional
-
1
attribute :last_run_result_file_name, Types::String.optional
-
1
attribute :last_run_date, Types::Date.optional
-
1
attribute :last_run_result_level, Types::String.optional
-
-
1
def self.build(script)
-
new(
-
object: script,
-
last_run_result_file: script.last_run_result_file,
-
last_run_result_file_name: script.last_run_result_file_name,
-
last_run_date: script.last_run_date,
-
last_run_result_level: script.last_run_result_level,
-
name: script.name,
-
)
-
end
-
end
-
-
1
option :script, Types.Instance(Script).constructor(Script.method(:build))
-
1
option :href, Types::String
-
1
option :scheduled, Types::Bool, default: -> { false }
-
-
1
renders_one :modal, ->(**args) {
-
modal = Modal.new(title: script.name, **args)
-
# Default trigger button
-
modal.with_trigger_button(
-
icon: "fa-solid fa-arrow-up-right-from-square",
-
tooltip_text: "Open",
-
href: href,
-
size: :small,
-
kind: :neutral,
-
type: :secondary,
-
)
-
modal
-
}
-
-
1
private
-
-
1
def description
-
then: 0
else: 0
if script.last_run_date.present?
-
tag.span(class: "flex gap-2") do
-
concat(tag.span do
-
concat(LooposUi::V2::Card.t(".script.last_run"))
-
concat(" ")
-
concat(script.last_run_date.strftime("%d-%m-%Y %H:%M"))
-
end)
-
then: 0
else: 0
concat(tag.i(class: "fa-regular fa-calendar-clock")) if scheduled
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module V2
-
1
class Image < LoopComponent
-
3027
IMAGE_MIMES = ::MIME::Types.select { |type| type.media_type == "image" }
-
-
1
option :editable, default: -> { false }, type: Types::Bool
-
1
option :image_url, optional: true
-
1
option :size, default: -> { :full }, type: Types::Coercible::Symbol.enum(:full, :small)
-
-
1
option :accept,
-
Types::Array | Types::Symbol.enum(:all),
-
optional: true,
-
default: -> { LooposUi.config.image_default_accept_formats }
-
-
1
option :model, optional: true
-
1
option :association, optional: true
-
-
1
option :rounded, default: -> { false }, type: Types::Bool
-
-
# TODO: only allowed when has_many_attached
-
1
option :list_view, default: -> { false }, type: Types::Bool
-
1
option :in_list_view, default: -> { false }, type: Types::Bool
-
1
option :in_list_view_index, Types::Integer, optional: true
-
-
1
option :attachment, optional: true
-
-
1
option :force_new, default: -> { false }, type: Types::Bool
-
-
# Permissions
-
1
option :can_detach, Types::Bool, default: -> { true }
-
-
1
option :path, Types::String, default: -> { LooposUi::Engine.routes.url_helpers.images_path }
-
-
1
option :turbo_frame_id, Types::String, optional: true
-
-
# HTML name for upload inputs. Defaults: has-one uses "file", has-many uses "files[]".
-
1
option :name, Types::Coercible::String, optional: true
-
-
# When set, file inputs use the HTML `form` attribute to associate with that id (no nested <form>).
-
1
option :form_id, Types::Coercible::String, optional: true
-
-
# Override Active Storage direct upload target (e.g. Core app URL when the page uses CoreApi).
-
# Expected form: `https://core.example.com/rails/active_storage/direct_uploads`
-
1
option :direct_upload_url, Types::Coercible::String.optional, optional: true
-
-
# When using a remote direct upload URL (e.g. Core), set `Authorization` on the create-blob XHR.
-
# LoopOS UI direct uploads use `X-Lui-Context` instead (see `upload_context_input_attributes`).
-
1
option :direct_upload_authorization, Types::Coercible::String.optional, optional: true
-
-
1
def initialize(...)
-
super
-
-
validate_input_source!
-
-
then: 0
else: 0
if list_view && !model
-
raise ArgumentError, "#{self.class}: Argument error, model is required when list_view is true"
-
end
-
-
validate_standalone_identity!
-
-
mimefy_accept_types
-
end
-
-
# When editable without a model ("standalone"), only `accept` is required for direct upload
-
# validation. LoopOS `images#create` attach still requires a full context with model keys.
-
1
def standalone_upload?
-
editable? && model.blank?
-
end
-
-
1
def context
-
payload =
-
then: 0
if model.present?
-
{
-
model_class: model.class.name,
-
model_id: model.id,
-
association: association,
-
accept: file_field_accept,
-
}
-
else: 0
else
-
{ accept: file_field_accept }
-
end
-
-
LooposUi::EncryptionService.encrypt(payload)
-
end
-
-
1
def file_field_accept
-
case @accept
-
when: 0
when :all
-
["image/*"]
-
else: 0
else
-
(IMAGE_MIMES & @accept).flat_map(&:content_type)
-
end.join(",")
-
end
-
-
# FIXME: small edge case here, if you pass a string like "png"
-
1
def mimefy_accept_types
-
then: 0
else: 0
return if accept == :all
-
-
@accept = accept.flat_map do |mime|
-
::MIME::Types.type_for(".#{mime}") || ::MIME::Types[mime.to_s]
-
end.compact_blank!
-
end
-
-
1
def unique_id
-
@unique_id ||= [
-
"multiple_image",
-
then: 0
else: 0
model.present? && association.present? ? dom_id(model, association) : nil,
-
standalone_identity_seed,
-
image_url,
-
size,
-
then: 0
else: 0
in_list_view ? "list_view" : nil,
-
in_list_view_index,
-
].compact.join("_")
-
end
-
-
1
def unique_list_id
-
@unique_list_id ||= "#{unique_id}_list"
-
end
-
-
1
def classes
-
[
-
"lui-image--#{size}",
-
then: 0
else: 0
rounded ? "lui-image--rounded" : nil,
-
then: 0
else: 0
empty_uploader? ? "always-edit" : nil,
-
].compact.join(" ")
-
end
-
-
1
def placeholder_classes
-
[
-
"lui-image__placeholder",
-
then: 0
else: 0
editable? ? "lui-image__placeholder--editable" : nil,
-
then: 0
else: 0
with_image? ? "hidden" : "flex",
-
].compact.join(" ")
-
end
-
-
1
def empty_uploader?
-
in_list_view && force_new
-
end
-
-
1
def attached_image
-
then: 0
else: 0
return attachment if attachment.present?
-
-
then: 0
if has_many_attached?
-
then: 0
else: 0
then: 0
else: 0
model&.send(association)&.first
-
else: 0
else
-
then: 0
else: 0
model&.send(association)
-
end
-
end
-
-
1
def with_attached?
-
then: 0
else: 0
return false if force_new
-
-
then: 0
if has_many_attached?
-
then: 0
else: 0
attached_image&.present?
-
else: 0
else
-
then: 0
else: 0
!!attached_image&.attached?
-
end
-
end
-
-
1
def turbo_frame_id
-
then: 0
else: 0
return @turbo_frame_id if @turbo_frame_id.present?
-
-
then: 0
else: 0
in_list_view ? unique_list_id : unique_id
-
end
-
-
1
def editable?
-
!!@editable
-
end
-
-
1
def single_file_field_name
-
name.presence || "file"
-
end
-
-
# When `name` is set, append "[]" for the multi-file input unless already present (Rails array param).
-
1
def multiple_file_field_name
-
then: 0
else: 0
return "files[]" if name.blank?
-
-
n = name.to_s
-
then: 0
else: 0
n.end_with?("[]") ? n : "#{n}[]"
-
end
-
-
# Root-relative path to the host application Active Storage direct upload endpoint.
-
# `file_field(..., direct_upload: true)` resolves `rails_direct_uploads_url` in the current
-
# routing context first; when the component is rendered under a mounted engine, that can
-
# produce a relative URL under the mount (e.g. `/mount/loopos_ui/rails/active_storage/direct_uploads`),
-
# which does not exist. The template sets `data-direct-upload-url` explicitly instead.
-
#
-
# Use `Rails.application.routes` only (path helper). Avoid `main_app.*_url` / mixed proxies so we
-
# never require `default_url_options[:host]` (e.g. mailers, previews without request host).
-
1
def active_storage_direct_upload_path
-
Rails.application.routes.url_helpers.rails_direct_uploads_path
-
rescue ActionController::UrlGenerationError, NoMethodError
-
"#{ActiveStorage.routes_prefix.to_s.chomp("/")}/direct_uploads"
-
end
-
-
1
def effective_direct_upload_url
-
direct_upload_url.presence || active_storage_direct_upload_path
-
end
-
-
# Encrypted direct-upload payload for X-Lui-Context. Omit `name` when linked to an outer form
-
# (`form_id` + standalone) so it is not submitted as `params[:context]` on the host item page.
-
1
def upload_context_input_attributes
-
attrs = {
-
type: "hidden",
-
autocomplete: "off",
-
value: context,
-
data: { images_target: "uploadContext" },
-
}
-
else: 0
then: 0
attrs[:name] = "context" unless standalone_upload? && form_id.present?
-
attrs
-
end
-
-
1
def with_image?
-
with_attached? || image_url.present?
-
end
-
-
1
def has_many_attached?
-
attachment_reflection == :has_many_attached
-
end
-
-
1
def has_one_attached?
-
attachment_reflection == :has_one_attached
-
end
-
-
1
def attachments
-
else: 0
then: 0
raise unless has_many_attached?
-
-
model.send(association).attachments
-
end
-
-
# Could probably generate this from the options, but this is fine for now
-
1
def configuration
-
{
-
editable: editable,
-
image_url: image_url,
-
size: size,
-
model: model,
-
association: association,
-
rounded: rounded,
-
list_view: list_view,
-
in_list_view: in_list_view,
-
attachment: attachment,
-
force_new: force_new,
-
can_detach: can_detach,
-
name: name,
-
form_id: form_id,
-
direct_upload_url: direct_upload_url,
-
direct_upload_authorization: direct_upload_authorization,
-
}
-
end
-
-
1
private
-
-
1
def validate_input_source!
-
then: 0
else: 0
return if model.present? || name.present?
-
-
raise ArgumentError, "#{self.class}: either model or name is required"
-
end
-
-
1
def validate_standalone_identity!
-
else: 0
then: 0
return unless standalone_upload?
-
then: 0
else: 0
return if turbo_frame_id.present? || name.present? || form_id.present?
-
-
raise ArgumentError, "#{self.class}: standalone editable mode requires name, form_id, or turbo_frame_id to avoid duplicate ids"
-
end
-
-
1
def standalone_identity_seed
-
else: 0
then: 0
return unless standalone_upload?
-
-
parts = [name.presence, form_id.presence].compact
-
then: 0
else: 0
return if parts.empty?
-
-
parts.join("_").parameterize(separator: "_")
-
end
-
-
1
def attachment_reflection
-
then: 0
else: 0
then: 0
else: 0
then: 0
else: 0
model&.class&.reflect_on_attachment(association.to_s)&.macro
-
end
-
end
-
end
-
end
-
then: 0
<% if list_view %>
-
<%= tag.turbo_frame id: turbo_frame_id, class: "lui-image-list" do %>
-
<div class="flex flex-row gap-2 flex-wrap">
-
<% attachments.each_with_index do |a,i| %>
-
<%= render LooposUi::V2::Image.new(
-
**configuration,
-
attachment: a,
-
list_view: false,
-
in_list_view: true,
-
in_list_view_index: i,
-
) %>
-
<% end %>
-
-
<%= render LooposUi::V2::Image.new(
-
**configuration,
-
force_new: true,
-
list_view: false,
-
in_list_view: true,
-
in_list_view_index: -1,
-
) %>
-
</div>
-
<% end %>
-
else: 0
<% else %>
-
<%= tag.turbo_frame id: turbo_frame_id do %>
-
<%= tag.div(
-
data: {
-
controller: "images",
-
images_editable_value: editable?,
-
images_standalone_value: standalone_upload?,
-
images_form_id_value: form_id.to_s,
-
images_with_attached_value: with_attached? || image_url.present?,
-
images_turbo_frame_id_value: turbo_frame_id,
-
images_unique_id_value: unique_id,
-
images_list_view_value: in_list_view,
-
images_has_many_value: has_many_attached?,
-
images_force_new_value: force_new,
-
images_direct_upload_authorization_value: direct_upload_authorization.presence || "",
-
images_direct_upload_url_value: effective_direct_upload_url,
-
images_urls_value: {
-
then: 0
else: 0
attach: standalone_upload? ? nil : path,
-
then: 0
else: 0
detach: (can_detach && !standalone_upload?) ? path : nil,
-
}
-
}) do %>
-
<%= tag.div(class: ["lui-image", classes].join(" "), data: { images_target: 'imageComponent', action: "pubsub:action@window->images#executeAction"}) do %>
-
<div
-
role="status"
-
class="hidden lui-image__loader"
-
data-images-target="loader"
-
>
-
<%= render LooposUi::Button.new(
-
icon: "spinner",
-
disabled: true,
-
size: :tiny,
-
kind: :neutral,
-
type: :secondary,
-
) %>
-
</div>
-
-
then: 0
<% if with_attached? %>
-
else: 0
<%= image_tag(attached_image, class: "lui-image__image", data: { images_target: "image", blob_id: attached_image.blob_id }) %>
-
then: 0
<% elsif image_url.present? %>
-
<%= image_tag(image_url, class: "lui-image__image", data: { images_target: "image" }) %>
-
else: 0
<% else %>
-
<%= image_tag("", class: "hidden lui-image__image", data: { images_target: "image" }) %>
-
<% end %>
-
-
<div
-
class="<%= placeholder_classes %>"
-
data-images-target="placeholder"
-
>
-
<%= render LooposUi::Icon.new(icon: :image, color: :white) %>
-
</div>
-
-
then: 0
else: 0
<% if editable? %>
-
<div class="overflow-visible lui-image__image-edit">
-
then: 0
<% if form_id.present? %>
-
<%= tag.input(**upload_context_input_attributes) %>
-
-
<div class="hidden" data-images-target="noImageUploader">
-
<%= render LooposUi::Button.new(
-
icon: :upload,
-
size: :tiny,
-
then: 0
else: 0
type: size == :full ? :secondary : :tertiary,
-
kind: :neutral,
-
then: 0
else: 0
tooltip_text: has_many_attached? ? "Upload images here" : "Upload image here",
-
tag_options: {
-
data: {
-
then: 0
else: 0
action: has_many_attached? ? "click->images#openFilePickerMultiple" : "click->images#openFilePicker"
-
} }
-
) %>
-
</div>
-
-
<div class="hidden" data-images-target="withImageUploader">
-
<%= render LooposUi::ActionMenu.new(
-
options: [
-
{ text: I18n.t("image.upload"), icon: "arrows-rotate", attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "openFilePicker"
-
}
-
}
-
},
-
then: 0
has_many_attached? ?
-
{ text: I18n.t("image.add"), icon: "plus", attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "openFilePickerMultiple"
-
}
-
else: 0
}
-
} : nil,
-
then: 0
can_detach ?
-
{ text: I18n.t("image.remove"), icon: "trash",
-
attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "detachImage"
-
}
-
else: 0
}
-
} : nil,
-
].compact
-
) do |menu| %>
-
<% menu.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
icon: "ellipsis-vertical",
-
size: :tiny,
-
then: 0
else: 0
type: size == :full ? :secondary : :tertiary,
-
kind: :neutral,
-
tag_options: { type: :button },
-
) %>
-
<% end %>
-
<% end %>
-
</div>
-
-
<%= file_field_tag single_file_field_name,
-
accept: file_field_accept,
-
class: "hidden",
-
form: form_id,
-
data: {
-
direct_upload_url: effective_direct_upload_url,
-
images_target: "file",
-
action: "change->images#previewAndSubmit",
-
} %>
-
-
<%= file_field_tag multiple_file_field_name,
-
accept: file_field_accept,
-
multiple: true,
-
class: "hidden",
-
form: form_id,
-
data: {
-
direct_upload_url: effective_direct_upload_url,
-
images_target: "fileMultiple",
-
action: "change->images#previewAndSubmitMultiple",
-
} %>
-
-
<%= submit_tag I18n.t("image.save"), form: form_id, data: { images_target: "submit" }, class: "hidden" %>
-
else: 0
<% else %>
-
<%= form_with url: "#", html: { data: { images_target: "form" } } do |f| %>
-
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
-
<%= tag.input(**upload_context_input_attributes) %>
-
-
<div class="hidden" data-images-target="noImageUploader">
-
<%= render LooposUi::Button.new(
-
icon: :upload,
-
size: :tiny,
-
then: 0
else: 0
type: size == :full ? :secondary : :tertiary,
-
kind: :neutral,
-
then: 0
else: 0
tooltip_text: has_many_attached? ? "Upload images here" : "Upload image here",
-
tag_options: {
-
data: {
-
then: 0
else: 0
action: has_many_attached? ? "click->images#openFilePickerMultiple" : "click->images#openFilePicker"
-
} }
-
) %>
-
</div>
-
-
<div class="hidden" data-images-target="withImageUploader">
-
<%= render LooposUi::ActionMenu.new(
-
options: [
-
{ text: I18n.t("image.upload"), icon: "arrows-rotate", attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "openFilePicker"
-
}
-
}
-
},
-
then: 0
has_many_attached? ?
-
{ text: I18n.t("image.add"), icon: "plus", attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "openFilePickerMultiple"
-
}
-
else: 0
}
-
} : nil,
-
then: 0
can_detach ?
-
{ text: I18n.t("image.remove"), icon: "trash",
-
attributes: {
-
data: {
-
action: "click->pubsub#publish",
-
pubsub_unique_id_param: unique_id,
-
pubsub_action_param: "detachImage"
-
}
-
else: 0
}
-
} : nil,
-
].compact
-
) do |menu| %>
-
<% menu.with_trigger do %>
-
<%= render LooposUi::Button.new(
-
icon: "ellipsis-vertical",
-
size: :tiny,
-
then: 0
else: 0
type: size == :full ? :secondary : :tertiary,
-
kind: :neutral,
-
tag_options: { type: :button },
-
) %>
-
<% end %>
-
<% end %>
-
</div>
-
-
<%= f.file_field single_file_field_name,
-
accept: file_field_accept,
-
class: "hidden",
-
data: {
-
direct_upload_url: effective_direct_upload_url,
-
images_target: "file",
-
action: "change->images#previewAndSubmit",
-
} %>
-
-
<%= f.file_field multiple_file_field_name,
-
accept: file_field_accept,
-
multiple: true,
-
class: "hidden",
-
data: {
-
direct_upload_url: effective_direct_upload_url,
-
images_target: "fileMultiple",
-
action: "change->images#previewAndSubmitMultiple",
-
} %>
-
-
<%= f.submit I18n.t("image.save"), data: { images_target: :submit }, class: "hidden" %>
-
<% end %>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
-
<span class="lui-image__error" data-images-target="error">
-
Error message
-
</span>
-
-
<%# <span class="lui-image__hint" data-upload-target="hint">Hint</span> %>
-
<% end %>
-
<% end %>
-
<% end %>
-
1
module LooposUi
-
1
module V2
-
1
class SelectBar < LoopComponent
-
1
erb_template <<~ERB
-
<nav class="flex w-full min-h-8 box-content py-1.5 pr-4 pl-6 items-center space-between bg-general-gray-100 border-b border-gray-200">
-
<%= content %>
-
</nav>
-
ERB
-
-
1
def render?
-
has_any_selectors?
-
end
-
-
1
def has_any_selectors?
-
content.present? && Nokogiri::HTML(content.to_s).search(".lui-select-bar__selector").any?
-
end
-
-
1
class Selector < LoopComponent
-
1
erb_template <<-ERB
-
<div class="lui-select-bar__selector">
-
<%= render LooposUi::FormEntry.new(label: label, orientation: :horizontal) do |fe| %>
-
<% fe.with_input do %>
-
<%= render LooposUi::Inputs::Select.new(
-
name: name,
-
options: options,
-
value: value,
-
placeholder: "Select..",
-
mode: :autosubmit,
-
)
-
%>
-
<% end %>
-
<% end %>
-
</div>
-
ERB
-
-
1
option :type, Types::Coercible::Symbol.enum(:core), default: -> { :core }
-
1
option :options, Types::Array.of(Types::Hash.schema(
-
value: Types::Coercible::String,
-
text: Types::String,
-
))
-
1
option :value, Types::Coercible::String
-
1
option :name, Types::Coercible::String
-
-
# TODO: these components are kinda hacked together
-
# and should be refactored internally. The FormEntry label does not support colors or icons, but
-
# with html_safe trick it works
-
1
def label
-
helpers.content_tag(:span, class: "text-app-800-primary") do
-
concat(helpers.content_tag(:icon, "", class: "fa-regular fa-map-pin mr-1"))
-
concat(type.to_s.capitalize)
-
concat(":")
-
end.html_safe
-
end
-
-
1
def render?
-
options.present? && options.count > 1
-
end
-
end
-
end
-
end
-
end
-
<%# HACK: Without this, the rows (and cells) do not render (in slotted mode) and cannot be captured in data_source method %>
-
<%# TODO: this is not necessary if we call the slots with <%= instead of <%. Investigate why %>
-
2
<% rows.each do |row| %>
-
6
<% capture do %>
-
6
<%= row %>
-
<% end %>
-
<% end %>
-
<%# ENDHACK %>
-
then: 0
else: 2
<% global_filters_html = capture do %>
-
<% global_filters.each do |filter| %>
-
<%# TODO type is igrored for now %>
-
<%# type, key, default %>
-
<span class="flex gap-1">
-
<span class="copy-12">
-
<%= filter[:text] %>
-
</span>
-
<%= render LooposUi::Toggle.new(
-
color: LooposUi::Colors.find("general-informative-800"),
-
name: filter[:key],
-
then: 0
else: 0
checked: filter[:default] == true ? "true" : "false")
-
%>
-
</span>
-
<% end %>
-
2
<% end if global_filters.present? %>
-
then: 0
else: 2
<% hiddable_columns_html = capture do %>
-
<% hiddable_columns.each do |column| %>
-
<span class="flex gap-1">
-
<span class="copy-12">
-
<%= column[:text] %>
-
</span>
-
<%= render LooposUi::Toggle.new(
-
color: LooposUi::Colors.find("general-informative-800"),
-
name: column[:key],
-
then: 0
else: 0
checked: column[:default] == true ? "true" : "false")
-
%>
-
</span>
-
<% end %>
-
2
<% end if hiddable_columns.present? %>
-
then: 0
else: 2
<%= render LooposUi::FilterBar.new(
-
search_options: filter_bar_search_options,
-
toggle_options: filter_bar_toggle_options,
-
show_toggle_switch: filter_bar_toggle_options.present?,
-
table_id: id
-
) do |bar| %>
-
<% filter_bar_filters.each do |key, value| %>
-
<% value.each do |text| %>
-
<%= render LooposUi::FilterPill.new(
-
text: "#{key}: #{text.humanize}",
-
state: :active,
-
hasCloseButton: true,
-
data: {
-
action: "click->table-filters#delete",
-
key: "#{key}_#{text}"
-
}
-
) %>
-
<% end %>
-
<% end %>
-
2
<% end if filter_bar_filters.present? %>
-
4
<%= content_tag(:div, id: unique_dom_id) do %>
-
4
<%= tag.div(class: classes, id: unique_dom_id, data: { controller: "table", "table-data-source-id-value": data_source_id, table_float_bar_outlet: "##{unique_dom_id} .lui-table__floating_bar .lui-float_bar", "table-columns-value": transformed_columns_with_actions }) do %>
-
2
<%= react_component("Table",
-
{
-
uniqueId: unique_dom_id,
-
id: id,
-
lang: I18n.locale,
-
columns: transformed_columns_with_actions,
-
then: 2
else: 0
searchQuery: params&.dig(:q, :search),
-
globalFiltersHtml: global_filters_html,
-
hiddableColumnsHtml: hiddable_columns_html,
-
tableHeader: LooposUi::V2::Table::Header.new(searchable: searchable, show_filters: false).render_in(view_context) do |table|
-
2
table.with_searchbar(
-
name: "table_#{id}_search_input",
-
then: 2
else: 0
value: params&.dig(:q, :search),
-
placeholder: search_placeholder || t(".search.placeholder")
-
)
-
end,
-
footerHtml: footer_html,
-
clearFiltersHtml: render(LooposUi::Button.new(
-
icon: :eraser,
-
text: t(".clear_all_filters"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :small,
-
)),
-
filterCounterTemplateHtml: render(LooposUi::Counter.new(count: ":count:", kind: :neutral)),
-
sortingEnabled: sortable,
-
view_more: view_more,
-
viewMoreHtml: capture {
-
2
render(LooposUi::Button.new(
-
text: t(".view_more"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :default,
-
))
-
},
-
**options
-
})
-
2
%>
-
2
then: 0
else: 2
<% if action_zone_enabled? %>
-
<div class="lui-table__floating_bar hidden" data-table-target="actionZone">
-
<%= action_bar %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
2
then: 2
else: 0
<%= render(LooposUi::Tooltip.new(
-
title: t(".expand_collapse_all"),
-
tippy_target_id: "#{unique_dom_id}-collapse-tooltip",
-
6
position: :top)) if data_source.any? { |row| row.key?(:children) }
-
2
%>
-
4
<%= content_tag(:div, id: data_source_id, class: "hidden") do %>
-
2
<%= data_source.to_json %>
-
<% end %>
-
4
<%= content_tag(:div, id: pagination_id, class: "hidden") do %>
-
2
<%= pagination.to_json %>
-
<% end %>
-
# frozen_string_literal: true
-
-
1
module LooposUi
-
1
module V2
-
1
class Table < LoopComponent
-
1
include Turbo::FramesHelper
-
-
1
DEFAULT_PAGE_SIZE = 15
-
-
1
renders_many :rows, ->(*args, **kwargs, &block) {
-
6
LooposUi::V2::Table::Row.new(*args, **kwargs, manageable_rows: manageable_rows, table: self, &block)
-
}
-
1
renders_one :action_bar, LooposUi::FloatBar
-
1
renders_one :footer, ->(*_args, **kwargs, &block) {
-
@footer_args = kwargs
-
capture(&block)
-
}
-
-
3
option :id, optional: true, type: Types::Coercible::String, default: -> { "" }
-
1
option :columns, type: Types::Array.of(Types::Hash), default: -> { [] }
-
3
option :data, default: -> { [] }
-
1
option :pagination, default: -> {
-
{
-
2
then: 2
else: 0
current: 1, pageSize: DEFAULT_PAGE_SIZE, total: data&.count || 0,
-
}
-
}
-
# TODO: move to pagination_config
-
3
option :show_pagination, default: -> { true }, type: Types::Bool
-
3
option :show_result_count, default: -> { true }, type: Types::Bool
-
3
option :show_pagination_size_changer, default: -> { true }, type: Types::Bool
-
-
3
option :show_header, default: -> { true }, type: Types::Bool
-
1
option :pagy, optional: true
-
1
option :selectable, optional: true
-
1
option :selectable_type, optional: true
-
3
option :searchable, default: -> { true }
-
1
option :search_placeholder, optional: true
-
3
option :sortable, default: -> { false }, type: Types::Bool
-
1
option :global_filters, optional: true
-
3
option :tree_indent_size, default: -> { 15 }
-
1
option :fetch_url, optional: true
-
1
option :filters_url, optional: true
-
3
option :view_more, default: -> { 10 }, type: Types::Integer
-
3
option :hidden_columns, optional: true, type: Types::Array.of(Types::Hash), default: -> { [] }
-
1
option :hiddable_columns, optional: true
-
-
# See https://ant.design/components/table#expandable
-
# Keys will be camelized when passing to the Antd Table component
-
1
option :expandable_config, optional: true, default: -> { {} } do
-
1
option :default_expanded_row_keys, optional: true, type: Types::Array.of(Types::Coercible::String)
-
1
option :default_expand_all_rows, optional: true, default: -> { false }, type: Types::Bool
-
# Should be used instead of tree_indent_size, but would be breaking change
-
1
option :indent_size, optional: true, type: Types::Coercible::Integer
-
end
-
-
3
option :manageable_rows, default: -> { false }, type: Types::Bool
-
1
option :add_new_url, optional: true
-
-
3
option :filter_bar_filters, optional: true, type: Types::Hash, default: -> { {} }
-
3
option :filter_bar_search_options, optional: true, type: Types::Hash, default: -> { {} }
-
3
option :filter_bar_toggle_options, optional: true, type: Types::Hash, default: -> { {} }
-
3
option :active_filters, optional: true, type: Types::Hash, default: -> { {} }
-
-
1
attr_reader :columns_hash
-
-
1
def initialize(**data)
-
then: 0
else: 2
data[:pagination] = {
-
current: data[:pagy].page,
-
pageSize: data[:pagy].items,
-
total: data[:pagy].count,
-
2
} if data[:pagy].present?
-
-
2
then: 0
else: 2
if data[:manageable_rows] && !data[:add_new_url]
-
raise ArgumentError, "add_new_url is required when manageable_rows is true"
-
end
-
-
2
super
-
end
-
-
1
def data_source
-
4
then: 2
@entries ||= if slotting_mode?
-
2
data_from_slotting
-
else: 0
else
-
@data.each do |row|
-
row.each do |key, value|
-
then: 0
else: 0
if value.is_a?(ViewComponent::Base)
-
row[key] = ApplicationController.new.view_context.render(value)
-
end
-
end
-
end
-
-
@data
-
end
-
-
4
@entries
-
end
-
-
# Additional options that will be passed in the Table.tsx
-
1
def options
-
{
-
2
fetchUrl: fetch_url,
-
filtersUrl: filters_url,
-
selectable: selectable,
-
selectableType: selectable_type,
-
showPagination: should_show_pagination?,
-
showPaginationSizeChanger: show_pagination_size_changer,
-
showResultCount: show_result_count,
-
searchable: searchable,
-
theme: theme,
-
treeIndentSize: tree_indent_size,
-
4
expandableConfig: expandable_config.to_h.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym },
-
initialFilters: active_filters.presence || {},
-
}
-
end
-
-
1
def transformed_columns
-
6
@transformed_columns ||= begin
-
# Create a set of dataIndex values from hidden_columns for efficient lookup
-
2
hidden_data_indices = hidden_columns.map { |hc| (hc[:dataIndex] || hc[:key]).to_s }.to_set
-
-
2
visible_columns = []
-
2
columns.each do |column|
-
6
column_key = (column[:dataIndex] || column[:key]).to_s
-
-
# Only add column if it's not in hidden_columns
-
6
else: 0
then: 6
unless hidden_data_indices.include?(column_key)
-
6
visible_columns << column
-
end
-
end
-
2
visible_columns.each_with_index do |column, i|
-
6
then: 0
else: 6
then: 0
else: 6
if column[:type]&.to_sym == :html
-
LooposUi.logger.warn("Setting the column type to :html is not necessary. It is the default and only value. You can safely remove it from the column definition. Column: {#{column.dig("title") || column.dig(:title)}}")
-
end
-
6
column[:type] ||= :html
-
6
column[:className] = [
-
column.dig(:className),
-
"lui-table__cell",
-
"lui-table__cell--n#{i}",
-
else: 2
case i
-
when: 2
when 0
-
2
"lui-table__cell--first"
-
when: 2
when visible_columns.size - 1
-
2
"lui-table__cell--last"
-
end,
-
].compact.join(" ")
-
-
6
then: 6
else: 0
column[:hidable] = true if column[:hidable].nil?
-
-
6
then: 0
else: 0
then: 0
else: 6
column.delete(:filters) if column.key?(:filters) && column.fetch(:filters)&.empty?
-
-
6
then: 0
else: 6
if column[:filters].present?
-
column[:filters] = column[:filters].deep_dup
-
column[:filterMode] = :tree
-
column[:filterSearch] = true
-
-
# Create composite key for the filter. Necessary for heterogeneous data
-
compose_filters(column[:filters], column)
-
-
column[:filters] = column[:filters].sort_by do |filter|
-
# sort_last: true — e.g. manual "empty" filter with "- (...)" text, which would otherwise sort first (ASCII)
-
then: 0
else: 0
sort_group = filter[:sort_last] ? 1 : 0
-
[sort_group, filter[:text].to_s]
-
end
-
column[:filters].each { |f| f.delete(:sort_last) }
-
else: 0
if column[:empty_filter]
-
then: 0
# Add empty filter
-
filter_key = column[:filter_key] || column[:dataIndex]
-
empty_filter = {
-
base_key: column[:dataIndex] || column[:filter_key],
-
type: :tag,
-
text: "- (#{t(".filter_empty").downcase})",
-
value: "empty",
-
}
-
empty_filter[:value] = "#{filter_key}$#{empty_filter[:value] || :_blank}"
-
column[:filters] << empty_filter
-
end
-
end
-
-
6
else: 2
then: 4
next unless column[:sortable]
-
-
2
column[:sorter] = true
-
2
column.delete(:sortable)
-
-
2
column[:sortKey] = column[:sort_key] || column[:dataIndex]
-
2
column.delete(:sort_key)
-
-
2
then: 0
else: 2
then: 0
else: 0
column[:defaultSortOrder] = column[:default_sort] == :asc ? "ascend" : "descend" if column[:default_sort]
-
2
column.delete(:default_sort)
-
end
-
2
visible_columns
-
end
-
end
-
-
1
def flat_filter_keys
-
extract_leaf_filters(transformed_columns.flat_map { |c| c[:filters] }.compact)
-
end
-
-
1
def extract_leaf_filters(tags)
-
tags.flat_map do |tag|
-
then: 0
else: 0
then: 0
if tag[:children]&.any?
-
extract_leaf_filters(tag[:children])
-
else: 0
else
-
{ key: tag[:value], text: tag[:text], base_key: tag[:base_key] }
-
end
-
end
-
end
-
-
1
def columns_hash
-
# TODO: remove dataIndex in favor of key. Use only one.
-
710
@columns_hash ||= transformed_columns.index_by { |c| (c[:dataIndex] || c[:key]).to_sym }
-
end
-
-
# FIXME: running this method before render will cause a bug where the slots are not rendered
-
1
def transformed_columns_with_actions
-
16
actions_count = rows.map { |row| row.actions.count }.max
-
-
4
then: 4
else: 0
then: 4
if actions_count&.positive?
-
4
transformed_columns + [action_column(actions_count)]
-
else: 0
else
-
transformed_columns
-
end
-
end
-
-
1
def unique_dom_id
-
138
@unique_dom_id ||= "table_#{rand(10 ** 10)}"
-
end
-
-
1
def indent_size
-
expandable_config.indent_size || tree_indent_size
-
end
-
-
1
private
-
-
# FIXME: see other fixmes in this file
-
# Warning!, due to a rendering bug (accessing slots before render),
-
# this can only be called after data_source, and after the rendering has begun
-
# There is a way to fix it, but it involves changes in the apps
-
# (can be automated). You probably do _not_ want to use this function
-
1
def rows_with_children?
-
6
rows.any? { |row| row.children.any? }
-
end
-
-
1
def should_show_pagination?
-
2
show_pagination
-
end
-
-
1
def action_zone_enabled?
-
2
selectable && action_bar?
-
end
-
-
1
def data_source_id
-
4
"#{id}_data_source"
-
end
-
-
1
def pagination_id
-
2
"#{id}_pagination"
-
end
-
-
1
def slotting_mode?
-
2
rows.any?
-
end
-
-
1
def data_from_slotting
-
2
@data_from_slotting ||= rows.map(&:data)
-
end
-
-
1
def compose_filters(filters, column, key_prefix: nil)
-
filters.map! do |filter|
-
filter = filter.deep_symbolize_keys
-
-
filter_key = filter[:key] || column[:filter_key] || column[:dataIndex]
-
filter[:value] = "#{filter_key}$#{filter[:value]}"
-
then: 0
else: 0
filter[:value] = "#{key_prefix}$#{filter[:value]}" if key_prefix.present?
-
then: 0
else: 0
if filter[:enabled]
-
column[:defaultFilteredValue] ||= []
-
# Only leaf keys belong in filteredValue. Parent rows are structural;
-
# a synthetic key like "Block name$" makes the Ant Design tree show all
-
# descendants as checked even when only some micro-states are persisted.
-
then: 0
else: 0
column[:defaultFilteredValue] << filter[:value].to_s if filter[:children].blank?
-
end
-
then: 0
else: 0
compose_filters(filter[:children], column, key_prefix: filter_key) if filter[:children].present?
-
-
filter
-
end
-
end
-
-
1
def classes
-
[
-
2
"lui-table",
-
2
then: 0
else: 2
!show_header ? "lui-table--no-header" : nil,
-
2
then: 2
else: 0
rows_with_children? ? "lui-table--tree-mode" : nil,
-
].compact.join(" ")
-
end
-
-
# Check https://ant.design/components/table#design-token
-
1
def theme
-
{
-
2
token: {
-
fontFamily: "IBM Plex Sans",
-
borderRadius: 4,
-
colorLink: find_color("general-global-black"),
-
colorActive: find_color("general-global-black"),
-
colorHover: find_color("general-global-black"),
-
colorText: find_color("general-global-black"),
-
},
-
components: {
-
Table: {
-
footerBg: find_color("general-global-white"),
-
headerBg: find_color("general-gray-100"),
-
-
headerColor: find_color("general-gray-900"),
-
headerSortActiveBg: find_color("general-gray-100"),
-
headerSortHoverBg: find_color("general-gray-100"),
-
headerSortSortBg: find_color("general-gray-100"),
-
bodySortBg: find_color("general-global-white"),
-
-
rowHoverBg: find_color("general-gray-100"),
-
-
borderColor: find_color("general-gray-200"),
-
headerBorderRadius: 4,
-
-
rowSelectedBg: find_color("general-gray-100"),
-
rowSelectedHoverBg: find_color("general-gray-100"),
-
selectionColumnWidth: 32,
-
headerSplitColor: find_color("general-gray-200"),
-
cellPaddingInlineSM: 16,
-
},
-
Pagination: {
-
itemActiveBg: "transparent",
-
},
-
},
-
}
-
end
-
-
1
def action_column(actions_count)
-
4
{
-
dataIndex: :_lui_actions,
-
key: :_lui_actions,
-
className: "lui-table__actions",
-
width: 0,
-
type: :html,
-
}
-
end
-
-
1
def default_sort_state
-
transformed_columns.find { |column| column[:default_sort] }
-
end
-
-
1
def footer_html
-
2
then: 0
else: 2
if manageable_rows || footer.present?
-
then: 0
else: 0
tag.div(class: @footer_args&.dig(:class)) do
-
then: 0
else: 0
concat(render(add_new_button)) if manageable_rows
-
then: 0
else: 0
concat(footer.to_s) if footer.present?
-
end
-
end
-
end
-
-
1
def add_new_button
-
LooposUi::Button.new(
-
icon: :plus,
-
text: t(".add_new_row"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :small,
-
href: add_new_url,
-
tag_options: { data: { target: "addNew" } },
-
)
-
end
-
-
1
class Row < LoopComponent
-
1
option :key
-
27
option :manageable_rows, default: -> { false }, type: Types::Bool
-
33
option :row_data, optional: true, type: Types::Hash, default: -> { {} }
-
1
option :table, optional: true
-
7
option :level, default: -> { 0 }
-
1
option :error_messages, optional: true, type: Types::Array.of(Types::String)
-
-
1
attr_writer :row_data
-
-
1
option :delete_action, optional: true do
-
1
option :method, type: Types::Coercible::String.enum("delete", "post", "patch"), default: -> { "delete" }
-
1
option :url, type: Types::String
-
end
-
-
1
erb_template <<-ERB
-
32
<% cells.each do |cell| %>
-
96
<%= cell %>
-
<% end %>
-
32
<% actions.each do |action| %>
-
32
<%= action %>
-
<% end %>
-
32
<% children.each do |child| %>
-
26
<%= child %>
-
<% end %>
-
32
ERB
-
1
renders_many :cells, ->(*args, **kwargs, &block) {
-
96
LooposUi::V2::Table::Cell.new(*args, row: self, table: table, **kwargs, &block)
-
}
-
1
renders_many :children, ->(*args, **kwargs, &block) {
-
26
LooposUi::V2::Table::Row.new(*args,**kwargs, table: table, level: level + 1, &block)
-
}
-
1
renders_many :actions, ->(&block) {
-
32
LooposUi::V2::Table::Cell.new(property: "_lui_action_cell", row: self, table: table, &block)
-
}
-
-
1
def initialize(**kwargs)
-
32
then: 0
else: 32
if kwargs[:manageable_rows] && !kwargs[:delete_action]
-
raise ArgumentError, "row: delete_action is required when manageable_rows is true"
-
end
-
-
32
super
-
end
-
-
# Inside LooposUi::V2::Table::Row
-
1
def data
-
32
@data ||= begin
-
32
then: 0
else: 32
if error_messages.present? && cells.any?
-
cells.first.error_messages = error_messages
-
end
-
-
32
datum = cells.to_h do |cell|
-
[
-
96
cell.property,
-
ApplicationController.new.view_context.render(cell),
-
]
-
end
-
-
# Check if we need to render the actions column for THIS row
-
32
then: 32
else: 0
if actions.any? || manageable_rows
-
32
actions_html = ""
-
-
# 1. Append custom actions if they exist
-
32
then: 32
else: 0
if actions.any?
-
32
actions_html += actions.map do |action|
-
32
<<-HTML
-
<div>
-
#{ApplicationController.new.view_context.render(action)}
-
</div>
-
HTML
-
end.join
-
end
-
-
# 2. Append the default delete button if rows are manageable
-
32
then: 0
else: 32
if manageable_rows
-
actions_html += <<-HTML
-
<div>
-
#{ApplicationController.new.view_context.render(LooposUi::Button.new(
-
tooltip_text: I18n.t(".delete_row"),
-
icon: :trash,
-
type: :tertiary,
-
kind: :neutral,
-
tag_options: {
-
data: {
-
row_key: key,
-
injected_delete_button: true,
-
},
-
},
-
))}
-
</div>
-
HTML
-
end
-
-
# 3. Assign the combined HTML to the actions cell
-
32
datum[:_lui_actions] = <<-HTML
-
<div class="lui-table__actions">
-
#{actions_html}
-
</div>
-
HTML
-
end
-
-
32
then: 14
else: 18
if children.any?
-
14
datum[:children] = children.map(&:data)
-
end
-
-
32
datum.merge!(non_cell_data)
-
end
-
end
-
-
1
def non_cell_data
-
{
-
32
_lui_key: key,
-
_lui_data: {
-
delete_action: delete_action,
-
**row_data,
-
},
-
}
-
end
-
-
# Have fun with this one
-
1
def tree_render(data,
-
level = 0,
-
get_children_fn:,
-
key_fn:,
-
render_row_fn:,
-
parents: [])
-
16
then: 6
else: 10
render_row_fn.call(self, data, level) if level.zero?
-
-
16
(get_children_fn.call(data) || []).each do |child|
-
26
with_child(key: key_fn.call(child, [*parents, data])) do |child_row|
-
26
render_row_fn.call(child_row, child, level + 1)
-
-
then: 10
else: 16
child_row.tree_render(
-
child,
-
level + 1,
-
get_children_fn: get_children_fn,
-
key_fn: key_fn,
-
render_row_fn: render_row_fn,
-
parents: [*parents, data],
-
26
) if (get_children_fn.call(child) || []).any?
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
256
<span class="<%= classes %>" style="<%= style %>" id="<%= unique_id %>" data-table-target="cell">
-
256
then: 0
else: 256
then: 0
else: 256
<% if error_messages.present? || error_messages&.length.to_i > 0 %>
-
<div class="absolute left-0 flex items-center justify-center">
-
then: 0
<% if error_messages.any?(&:present?) %>
-
<%= render LooposUi::IconTooltip.new(icon: :pipe, size: "16", icon_color: :danger) do |icon_tooltip| %>
-
<% icon_tooltip.with_custom_tooltip do %>
-
<%= render LooposUi::Tooltip.new do |tooltip| %>
-
<div class="flex flex-col gap-1">
-
<% error_messages.each do |message| %>
-
<span><%= message %></span>
-
<% end %>
-
</div>
-
<% end %>
-
<% end %>
-
<% end %>
-
else: 0
<% else %>
-
<%= render LooposUi::IconTooltip.new(icon: :pipe, size: "16", icon_color: :danger) %>
-
<% end %>
-
</div>
-
<% end %>
-
256
<div class="lui-table__cell-content_slot">
-
256
<%= content_or_dash %>
-
</div>
-
<div class="flex flex-row items-center justify-center">
-
256
then: 0
else: 256
<%= actions if actions.present? %>
-
</div>
-
</span>
-
1
module LooposUi
-
1
module V2
-
1
class Table
-
1
class Cell < LoopComponent
-
1
option :row
-
1
option :table, optional: true
-
1
option :property
-
1
option :error_messages, optional: true, type: Types::Array.of(Types::String)
-
-
1
renders_one :actions
-
-
1
private
-
-
1
def classes
-
[
-
256
"lui-table__cell-content",
-
256
then: 0
else: 256
error_messages.present? ? "lui-table__cell-content-error" : nil,
-
].compact.join(" ")
-
end
-
-
1
def content_or_dash
-
256
then: 0
else: 256
return content if action_cell?
-
256
then: 0
else: 256
return "-" if content.blank? || effectively_empty?
-
-
256
try_typed_columns || content
-
end
-
-
1
def try_typed_columns
-
256
then: 64
else: 192
return if column.blank?
-
-
192
else: 192
case column[:type]
-
when: 0
when :date
-
render(LooposUi::DateShow.new(date: content, format: :short))
-
end
-
end
-
-
1
def effectively_empty?
-
256
fragment = Nokogiri::HTML::DocumentFragment.parse(content.to_s)
-
-
256
fragment.children.all? do |c|
-
256
text_blank = c.inner_text.strip.empty?
-
-
256
has_visible_elements = c.at_css("i, img, iframe, video, object, embed, [data-react-class], [src], [href]")
-
-
256
text_blank && has_visible_elements.nil?
-
end
-
end
-
-
1
def unique_id
-
256
then: 128
else: 0
@unique_id ||= "#{table&.unique_dom_id}_cell_#{rand(10**10)}"
-
end
-
-
1
def column
-
704
then: 704
else: 0
then: 704
else: 0
table&.columns_hash&.dig(property)
-
end
-
-
1
def action_cell?
-
256
property == "_lui_actions"
-
end
-
-
# TODO: sometimes, we do not have the table reference :/
-
# Maybe we could pass the column as an option, but this feels convoluted
-
# Cells should not be rendered without tables, but we need this to manageable_rows functionality
-
1
def style
-
256
then: 192
else: 64
then: 192
else: 64
then: 192
else: 64
column&.slice(:width, :max_width, :min_width)
-
&.map { |k, v| "#{k.to_s.dasherize}: #{v.to_i - table.indent_size * row.level}px;" }
-
&.join(" ")
-
end
-
end
-
end
-
end
-
end
-
2
<div class="lui-table_header" data-controller="table-filters">
-
2
then: 2
else: 0
<% if searchbar.present? && searchable %>
-
2
<div class="w-80">
-
2
<%= searchbar %>
-
</div>
-
<% end %>
-
then: 0
else: 2
<% filters.each do |filter| %>
-
<%= filter %>
-
2
<% end if show_filters %>
-
2
<div class="hidden" data-table-filters-target="button">
-
2
<%= render LooposUi::Button.new(
-
icon: :eraser,
-
text: t(".clear_all_filters"),
-
type: :tertiary,
-
kind: :neutral,
-
size: :small,
-
tag_options: {
-
data: { action: "click->table-filters#clearAllFilters" }
-
}
-
2
) %>
-
</div>
-
</div>
-
1
module LooposUi
-
1
module V2
-
1
class Table
-
1
class Header < LoopComponent
-
1
option :show_filters, Types::Bool, default: -> { true }
-
1
option :searchable, Types::Bool, default: -> { true }
-
1
renders_one :searchbar, LooposUi::Inputs::Search
-
1
renders_many :filters, LooposUi::TableFilter
-
-
3
then: 0
else: 0
delegate :t, to: LooposUi::V2::Table
-
end
-
end
-
end
-
end
-
1
module LooposUi
-
1
class Wysiwyg < LoopComponent
-
1
option :initial_value, optional: true
-
13
option :input_name, default: -> { "" }
-
9
option :small, default: -> { false }
-
11
option :language, default: -> { "en" }
-
1
option :app, optional: true
-
-
1
mod :small
-
13
mod :neutral_small, condition: -> { app_kind.to_s == "neutral" && small? }
-
-
1
private
-
-
1
def small?
-
16
small || false
-
end
-
-
1
def app_kind
-
24
app || LooposUi.config.try(:app_type) || "core"
-
end
-
end
-
end
-
12
<% dropkiq_preview_id = "wysiwyg-#{input_name}" %>
-
24
<%= tag.div(class: classes) do %>
-
12
<%= react_component(
-
"Wysiwyg",
-
{
-
currentValue: initial_value,
-
name: input_name,
-
isSmallTheme: small?,
-
app: app_kind,
-
lang: language,
-
dropkiqPreviewId: dropkiq_preview_id
-
}
-
12
) %>
-
<% end %>
-
12
<!-- div id="<%= dropkiq_preview_id %>" class='dropkiq-preview'></!-->