Tree branch

Create app

rails new flowpulse_branch -j importmap --database=postgresql
cd flowpulse_branch

bundle add css-zero

bin/rails generate css_zero:install

bin/rails generate css_zero:add --help

bin/rails g authentication

User.create(email_address: "mario@mario.it", password: "123456")


rails g controller Pages home 
rails g scaffold Category user:references Category name:string description:text icon

rails g scaffold Mycategory user:references category:references name:string description:text icon public:boolean

rails g scaffold Branch user:references slug parent_id:integer position content_id:integer slug_content user_content_username child_id:integer mycategory:references 
#user.rb
class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: ->(e) { e.strip.downcase }
  has_many :mycategories
  has_many :categories
  has_many :branches
end


class Branch < ApplicationRecord
  belongs_to :user
  belongs_to :mycategory, optional: true

  # Gerarchia dell'albero (Parent-Child)
  belongs_to :parent, class_name: "Branch", foreign_key: "parent_id", optional: true
  has_many :children, class_name: "Branch", foreign_key: "parent_id", dependent: :destroy

  # Collegamenti tra rami separati
  belongs_to :link_child, class_name: "Branch", foreign_key: "child_id", optional: true
  has_many :linked_parents, class_name: "Branch", foreign_key: "child_id"

  # Gestisce l'ordine tra i figli dello stesso parent
  acts_as_list scope: :parent 

  # Validazioni
  validates :slug, presence: true, uniqueness: true
  validates :slug_content, uniqueness: true, allow_nil: true
end

# branches_controller.rb

Gem act as list

gem ‘acts_as_list’

helpers

# helpers_branch.rb

def root(id)
  Branch.find(id)

end 


nestable sortjs

branches/show.html.erb

bin/importmap pin sortablejs
bin/rails generate stimulus sortable


<div data-controller="sortable">
  <div id="nestedDemo" class="list-group col nested-sortable">
    <div class="list-group-item nested-1">Item 1.1
      <div class="list-group nested-sortable">
        <div class="list-group-item nested-2">Item 2.1</div>
        <div class="list-group-item nested-2">Item 2.2</div>
      </div>
    </div>
    <div class="list-group-item nested-1">Item 1.2</div>
  </div>
</div>

sortable.css

  .list-group {
    display: block;
    padding: 10px;
    border: 1px solid #ddd;
    background-color: #f8f8f8;
    margin-bottom: 10px;
  }

  .list-group-item {
    padding: 10px;
    background: white;
    border: 1px solid #ccc;
    margin: 5px 0;
    cursor: grab;
    border-radius: 5px;
  }

  /* Quando l'elemento viene trascinato */
  .sortable-ghost {
    opacity: 0.5;
    background: #ddd;
  }

# branches_controller.rb
   def updateposition
    @branch_root = @branch.root
    new_position = params[:position].to_i
    parent_id = params[:parent_id]

    respond_to do |format|
      if @branch.update(parent_id: parent_id)
        # acts_as_list è 1-based, ma se il valore è 0, forziamo a 1
        @branch.insert_at(new_position)

        format.html { redirect_to @branch_root } # , notice:  "Branch spostato con successo." }
        format.json { render :show, status: :ok, location: @branch_root }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @branch.errors, status: :unprocessable_entity }
      end
    end
  end

_sortable.html.erb

<div data-controller="sortable">
  <ul class="nested-sortable" data-sortable-target="list" data-id="<%= @branch.id %>">
    <%= render partial: "child", collection: @branch.children, as: :branch, locals: { parent_id: @branch.id } %>
  </ul>
</div>

_child.html.erb

<li class="list-group-item" data-id="<%= branch.id %>">
  <span class="drag-handle"></span> <%= branch.slug %>
  <% if branch.children.any? %>
    <ul class="nested-sortable" data-sortable-target="list">
      <%= render partial: "child", collection: branch.children, as: :branch %>
    </ul>
    <% else %>
    <ul class="nested-sortable" data-sortable-target="list">
    </ul>
  <% end %>
</li>
// sortable_controller.js

import { Controller } from "@hotwired/stimulus";
import Sortable from "sortablejs";

export default class extends Controller {
  static targets = ["list"];

  connect() {
    console.log("SortableJS initializing...");
    console.log(this.listTargets); // Debug

    this.listTargets.forEach(container => {
      new Sortable(container, {
        group: {
          name: "nested",
          pull: true, 
          put: true
        },
        animation: 150,
        fallbackOnBody: true,
        swapThreshold: 0.65,
        handle: ".drag-handle",
        onEnd: (event) => this.updatePosition(event)
      });
    });
  }

  async updatePosition(event) {
    const item = event.item;
    const parent = item.closest(".nested-sortable")?.closest(".list-group-item");
    const newParentId = parent ? parent.dataset.id : null;
    const newPosition = event.newIndex + 1;
  
    console.log("Item ID:", item.dataset.id);
    console.log("New Parent ID:", newParentId);
    console.log("New Position:", newPosition);
  
    try {
      const response = await fetch(`/branches/${item.dataset.id}/update_position`, {
        method: "PATCH",
        headers: { 
          "Content-Type": "application/json", 
          "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content 
        },
        body: JSON.stringify({ 
          position: newPosition, 
          parent_id: newParentId 
        }),
      });
  
      if (response.ok) {
        const data = await response.json();
        console.log("Updated successfully:", data);
  
        if (data.root_id) {
          window.location.href = `/branches/${data.root_id}`;  // Redirect alla root
        }
      } else {
        console.error("Update failed:", response);
      }
    } catch (error) {
      console.error("Request failed:", error);
    }
  }
} 


gem ‘annotate’

<!--_child.html.erb-->
<li class="list-group-item" data-id="<%= branch.id %>">
   <%= branch.position %> - <span class="drag-handle"></span> 
  - <%= branch.slug %> 
  <% if branch.children.any? %>
    <ul class="nested-sortable" data-sortable-target="list">
      <%= render partial: "child", collection: branch.children.order(:position), as: :branch %>
    </ul>
    <% else %>
    <ul class="nested-sortable" data-sortable-target="list">
    </ul>
  <% end %>
</li>

<!--_sortable.html.erb-->
<div data-controller="sortable">
  <li class="list-group-item" data-id="<%= @branch.id %>" data-parent-id="<%= @branch.parent_id %>">
    <span class="drag-handle"></span> <%= @branch.slug %>
    <ul class="nested-sortable" data-sortable-target="list">
      <%= render partial: "child", collection: @branch.children.order(:position), as: :branch %>
    </ul>
  </li>
</div>

# branches_controller.rb
  def update_position
    parent_id = params[:parent_id].presence || nil
    new_position = params[:position].to_i
  
    if @branch.update(parent_id: parent_id, position: new_position)
      @branch.insert_at(new_position)  # Forza la posizione
      
      # Trova la radice della gerarchia dopo l'aggiornamento
      root_branch = @branch.root
  
      respond_to do |format|
        format.json { render json: { success: true, position: @branch.position, parent_id: @branch.parent_id, root_id: root_branch.id } }
        format.html { redirect_to branch_path(root_branch), notice: "Branch aggiornato con successo!" }
      end
    else
      respond_to do |format|
        format.json { render json: { success: false, errors: @branch.errors.full_messages }, status: :unprocessable_entity }
        format.html { redirect_back fallback_location: branches_path, alert: "Errore nell'aggiornamento della posizione." }
      end
    end
  end

# branch.rb
class Branch < ApplicationRecord
  belongs_to :user
  belongs_to :mycategory, optional: true

  has_many :category, through: :mycategory

  # Gerarchia dell'albero (Parent-Child)
  belongs_to :parent, class_name: "Branch", foreign_key: "parent_id", optional: true
  has_many :children, class_name: "Branch", foreign_key: "parent_id"

  # Collegamenti tra rami separati
  belongs_to :link_child, class_name: "Branch", foreign_key: "child_id", optional: true
  has_many :linked_parents, class_name: "Branch", foreign_key: "child_id", dependent: :nullify

  # Gestisce l'ordine tra i figli dello stesso parent
  acts_as_list scope: :parent_id

  # Validazioni
  validates :slug, presence: true, uniqueness: true
  #  validates :slug_note, uniqueness: true, allow_nil: true
  def root
    self.class.where(id: self_and_ancestors_ids.first).first
  end

  def self_and_ancestors_ids
    ids = [ id ]
    current = self
    while current.parent_id.present?
      ids.unshift(current.parent_id)
      current = current.parent
    end
    ids
  end
end


Controllare perchè non fa il redirect

Mettere la vista ad albero e la vista d3j

Vedere per il new children

vedere per le categorie

Contatto Progetti

Capitoli

Gli Eventi lasciarli su 1 impegno o spostarli ? Transazioni Contatti ecc.

branch_helper.rb

module BranchesHelper def parent_chain(id) parent_ids = [] branch = Branch.find_by(id: id)

while branch&.parent_id
  parent_ids << branch.parent_id
  branch = Branch.find_by(id: branch.parent_id)
end

parent_ids.reverse   end   def tree_to_hash(branch)
return {} if branch.nil?

{
  id: branch.id,  # <-- Aggiunto ID per il link corretto
  name: branch.slug,
  icon: branch.mycategory&.icon || '➖',
  children: branch.children.map { |child| tree_to_hash(child) }
}   end   def hash_to_ascii(tree, prefix = "", parent_prefix = "")
return "" if tree.nil? || tree.empty?

# Creiamo un link con l'ID corretto del nodo
node_link = "<a href='/branches/#{tree[:id]}'>#{tree[:icon]} #{tree[:name]}</a>"
tree_content = "#{prefix}#{node_link}\n"

tree[:children].each_with_index do |child, index|
  is_last = index == tree[:children].length - 1
  child_prefix = parent_prefix + (is_last ? "└── " : "├── ")
  new_parent_prefix = parent_prefix + (is_last ? "    " : "│   ")

  tree_content += hash_to_ascii(child, child_prefix, new_parent_prefix)
end

tree_content   end

def markdown(text) renderer = Redcarpet::Render::HTML.new(hard_wrap: true, filter_html: true) md = Redcarpet::Markdown.new(renderer, autolink: true, tables: true) md.render(text).html_safe end end

27 mar 2025

rails generate migration RemoveMycategoryFromBranches mycategory:references

rails generate migration DropCategoriesAndMycategories
rails destroy scaffold Mycategory
rails destroy scaffold Category
class DropCategoriesAndMycategories < ActiveRecord::Migration[8.0]
  def change
    remove_foreign_key :mycategories, :categories
    drop_table :categories
    drop_table :mycategories
  end
end

Api


rails generate scaffold ExternalPost branch:references  api_variabile:string content:json
rails g migration RemoveIconFromBranches icon:string

rails g migration AddRootToBranches index_branch:boolean



```sh
rails g controller api/v1/trees  --skip-template-engine --skip-assets --skip-helper

rails generate migration AddDefaultsToBranches
rails generate migration AddStructureToBranches structure:boolean:default:false

branch_root_id → radice della struttura

branch_id → nodo a cui è collegato (es. progetto, modulo)

actor_ids → array JSON di user o ruoli coinvolti

📌 Task (impegno operativo) bash Copia Modifica rails g model Task content_id:integer user_id:integer status:string start_date:datetime due_date:datetime user_id → assegnatario

content_id → cosa deve fare (la compilazione o blocco informativo)

🔁 Request (richiesta o passaggio) bash Copia Modifica rails g model Request task_id:integer status:string payload:jsonb può essere usata per workflow tra task (es. approvazioni, richieste info)

🗓️ DataEvent (evento reale) bash Copia Modifica rails g model DataEvent task_id:integer user_id:integer data:datetime place_id:integer contact_id:integer transaction_id:integer body:jsonb Quando un Task è completato → si registra qui

Collegabile a:

Place

Contact

Transaction

💶 Transaction bash Copia Modifica rails g model Transaction data:datetime amount:decimal currency:string description:string 📍 Place bash Copia Modifica rails g model Place name:string address:string geolocation:jsonb 👤 Contact bash Copia Modifica


Table of contents