Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions app/controllers/strategies_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
class StrategiesController < ApplicationController
before_action :set_strategy, only: [ :show, :edit, :update, :destroy ]

def index
@strategies = Strategy.all
end

def show;end

def new
@strategy = Strategy.new
@wallets = current_user.wallets
end

def create
@strategy = Strategy.new(strategy_params)

if @strategy.save
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategies_path, notice: "Strategy created successfully!" }
end
else
render :new, status: :unprocessable_entity
end
end

def edit;end

def update
if @strategy.update(strategy_params)
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategies_path, notice: "Strategy updated successfully!" }
end
else
render :edit, status: :unprocessable_entity
end
end

def destroy
@strategy.destroy
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategies_path, notice: "Strategy deleted successfully!" }
end
end

private

def strategy_params
params.require(:strategy).permit(:title, :wallet_id)
end

def set_strategy
@strategy = Strategy.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to strategies_path, alert: "Strategy not found!"
end
end
64 changes: 64 additions & 0 deletions app/controllers/strategy_rules_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class StrategyRulesController < ApplicationController
before_action :set_strategy_rule, only: [ :show, :edit, :update, :destroy ]
before_action :set_strategies, only: [ :new, :create, :edit, :update ]

def index
@strategy_rules = StrategyRule.all
end

def show; end

def new
@strategy_rule = StrategyRule.new
end

def create
@strategy_rule = StrategyRule.new(strategy_rules_params)

if @strategy_rule.save
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategy_rules_path, notice: "Strategy rule created successfully!" }
end
else
render :new, status: :unprocessable_entity
end
end

def edit; end

def update
if @strategy_rule.update(strategy_rules_params)
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategy_rules_path, notice: "Strategy rules updated successfully!" }
end
else
render :edit, status: :unprocessable_entity
end
end

def destroy
@strategy_rule.destroy
respond_to do |format|
format.turbo_stream
format.html { redirect_to strategy_rules_path, notice: "Strategy rule deleted successfully!" }
end
end

private

def strategy_rules_params
params.require(:strategy_rule).permit(:strategy_id, :asset_kind, :min_percentage, :max_percentage, :rule_type)
end

def set_strategy_rule
@strategy_rule = StrategyRule.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to strategy_rules_path, alert: "Strategy rule not found!"
end

def set_strategies
@strategies = Strategy.all
end
end
22 changes: 22 additions & 0 deletions app/javascript/controllers/rule_type_toggle_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
connect() {
this.toggle()
}

toggle() {
const percentageRadio = this.element.querySelector('input[value="percentage"]')
const prohibitionRadio = this.element.querySelector('input[value="prohibition"]')
const percentageFields = this.element.querySelector('#percentage-fields')
const prohibitionMessage = this.element.querySelector('#prohibition-message')

if (percentageRadio && percentageRadio.checked) {
if (percentageFields) percentageFields.classList.remove('hidden')
if (prohibitionMessage) prohibitionMessage.classList.add('hidden')
} else if (prohibitionRadio && prohibitionRadio.checked) {
if (percentageFields) percentageFields.classList.add('hidden')
if (prohibitionMessage) prohibitionMessage.classList.remove('hidden')
}
}
}
42 changes: 42 additions & 0 deletions app/models/strategy_rule.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
class StrategyRule < ApplicationRecord
belongs_to :strategy

attr_accessor :rule_type
attr_accessor :min_percentage # Temporário até criar a migration

validates :asset_kind, presence: true
validates :strategy_id, presence: true
validate :validate_percentage_rule, if: -> { rule_type == 'percentage' }

Check failure on line 9 in app/models/strategy_rule.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
validate :validate_prohibition_rule, if: -> { rule_type == 'prohibition' }

Check failure on line 10 in app/models/strategy_rule.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

# Métodos auxiliares
def prohibition?
max_percentage.nil? && (min_percentage.nil? || min_percentage.blank?)
end

def percentage_rule?
max_percentage.present? || min_percentage.present?
end

private

def validate_percentage_rule
if min_percentage.present? && max_percentage.present?
if min_percentage.to_f > max_percentage.to_f
errors.add(:min_percentage, "deve ser menor ou igual à porcentagem máxima")
end
if min_percentage.to_f < 0 || min_percentage.to_f > 100
errors.add(:min_percentage, "deve estar entre 0 e 100")
end
if max_percentage.to_f < 0 || max_percentage.to_f > 100
errors.add(:max_percentage, "deve estar entre 0 e 100")
end
elsif min_percentage.blank? && max_percentage.blank?
errors.add(:base, "Por favor, informe pelo menos a porcentagem mínima ou máxima")
end
end

def validate_prohibition_rule
# Para proibição, não deve ter porcentagens
if min_percentage.present? || max_percentage.present?
errors.add(:base, "Regras de proibição não devem ter porcentagens definidas")
end
end
end
1 change: 1 addition & 0 deletions app/models/wallet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Wallet < ApplicationRecord
belongs_to :user
has_many :holdings
has_many :transactions
has_one :strategy

enum :status, { active: 1, inactive: 0 }
validates :name, presence: true
Expand Down
4 changes: 2 additions & 2 deletions app/views/shared/dashboard/_sidebar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
<span>Assets</span>
<% end %>

<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-white/10 transition">
<%= link_to strategies_path, class: "flex items-center space-x-3 px-4 py-3 rounded-lg transition #{current_page?(strategies_path) || controller_name == 'strategies' ? 'bg-yellow-400 text-gray-900' : 'hover:bg-white/10'}" do %>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span>Strategies</span>
</a>
<% end %>

<a href="#" class="flex items-center space-x-3 px-4 py-3 rounded-lg hover:bg-white/10 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand Down
37 changes: 37 additions & 0 deletions app/views/strategies/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<%= form_with(model: strategy, data: { turbo_frame: "_top" }, class: "space-y-6") do |form| %>
<% if strategy.errors.any? %>
<div class="bg-red-50 border-2 border-red-200 text-red-800 px-6 py-4 rounded-xl">
<h3 class="font-bold mb-2"><%= pluralize(strategy.errors.count, "error") %> found:</h3>
<ul class="list-disc list-inside">
<% strategy.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>

<div>
<%= form.label :wallet_id, "Wallet", class: "block text-sm font-bold text-gray-700 mb-2" %>
<%= form.select :wallet_id,
options_from_collection_for_select(@wallets, :id, :name, strategy.wallet_id),
{ prompt: "Select a wallet" },
class: "w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 transition-all outline-none" %>
</div>

<div>
<%= form.label :title, "Strategy Title", class: "block text-sm font-bold text-gray-700 mb-2" %>
<%= form.text_field :title,
class: "w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 transition-all outline-none",
placeholder: "e.g. Conservative Portfolio",
autofocus: true %>
</div>

<div class="flex gap-4 pt-4">
<%= form.submit "Save",
class: "flex-1 bg-gradient-to-r from-yellow-500 to-amber-500 hover:from-yellow-600 hover:to-amber-600 text-white font-bold py-4 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105 cursor-pointer" %>
<%= link_to "Cancel",
strategies_path,
class: "flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-4 rounded-xl text-center transition-all",
data: { turbo_frame: "_top" } %>
</div>
<% end %>
20 changes: 20 additions & 0 deletions app/views/strategies/_strategy.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div id="<%= dom_id(strategy) %>" class="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:scale-105 border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-r from-yellow-500 to-amber-500 px-6 py-4">
<h3 class="text-xl font-bold text-white">
<%= strategy.title %>
</h3>
</div>

<div class="p-6">
<div class="mb-4">
<p class="text-sm font-semibold text-gray-500 mb-1">Wallet</p>
<p class="text-lg text-gray-900"><%= strategy.wallet.name %></p>
</div>

<div class="flex gap-2 pt-4 border-t border-gray-200">
<%= link_to "View", strategy_path(strategy), class: "flex-1 bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded-lg text-center transition-all" %>
<%= link_to "Edit", edit_strategy_path(strategy), class: "flex-1 bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded-lg text-center transition-all", data: { turbo_frame: "modal" } %>
<%= button_to "Delete", strategy_path(strategy), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "flex-1 bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg transition-all" %>
</div>
</div>
</div>
11 changes: 11 additions & 0 deletions app/views/strategies/create.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<%= turbo_stream.append "strategies" do %>
<%= render @strategy %>
<% end %>

<%= turbo_stream.update "modal", "" %>

<%= turbo_stream.update "flash" do %>
<div class="bg-green-500 text-white px-6 py-4 rounded-md shadow-lg mb-4 animate-fade-in">
✓ Strategy created successfully!
</div>
<% end %>
7 changes: 7 additions & 0 deletions app/views/strategies/destroy.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= turbo_stream.remove dom_id(@strategy) %>

<%= turbo_stream.update "flash" do %>
<div class="bg-red-500 text-white px-6 py-4 rounded-md shadow-lg mb-4 animate-fade-in">
✓ Strategy deleted successfully!
</div>
<% end %>
19 changes: 19 additions & 0 deletions app/views/strategies/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= turbo_frame_tag "modal" do %>
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="border-b border-gray-200 px-6 py-4">
<div class="flex justify-between items-center">
<h2 class="text-2xl font-bold text-gray-900">Edit Strategy</h2>
<%= link_to "✕",
strategies_path,
class: "text-gray-400 hover:text-gray-600 text-2xl font-bold",
data: { turbo_frame: "_top" } %>
</div>
</div>

<div class="px-6 py-4">
<%= render "form", strategy: @strategy %>
</div>
</div>
</div>
<% end %>
45 changes: 45 additions & 0 deletions app/views/strategies/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-10">
<div>
<h1 class="text-5xl font-black bg-gradient-to-r from-yellow-600 via-yellow-500 to-amber-500 bg-clip-text text-transparent mb-3">My Strategies</h1>
<p class="text-gray-500 text-lg font-medium">Manage your investment strategies</p>
</div>
<%= link_to new_strategy_path,
class: "group relative inline-flex items-center justify-center gap-2 bg-gradient-to-r from-yellow-500 to-amber-500 hover:from-yellow-600 hover:to-amber-600 text-white font-bold py-4 px-8 rounded-xl shadow-2xl hover:shadow-yellow-500/50 transition-all duration-300 transform hover:scale-105",
data: { turbo_frame: "modal" } do %>
<svg class="w-6 h-6 transition-transform group-hover:rotate-90 duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
New Strategy
<% end %>
</div>
<%= turbo_frame_tag "modal" %>
<% if @strategies.any? %>
<div id="strategies" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<%= render @strategies %>
</div>
<% else %>
<div class="relative overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100 border-2 border-dashed border-gray-300 rounded-3xl p-16 text-center">
<div class="absolute top-0 right-0 -mt-4 -mr-4 w-32 h-32 bg-yellow-200 rounded-full opacity-20 blur-3xl"></div>
<div class="absolute bottom-0 left-0 -mb-4 -ml-4 w-32 h-32 bg-amber-200 rounded-full opacity-20 blur-3xl"></div>
<div class="relative">
<div class="inline-flex p-8 bg-gradient-to-br from-yellow-100 to-amber-100 rounded-3xl mb-6 shadow-lg">
<svg class="w-20 h-20 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/>
</svg>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-3">No strategies registered yet</h3>
<p class="text-gray-600 text-lg mb-8 max-w-md mx-auto">Start building your portfolio with a custom investment strategy!</p>
<%= link_to new_strategy_path,
class: "group inline-flex items-center gap-3 bg-gradient-to-r from-yellow-500 to-amber-500 hover:from-yellow-600 hover:to-amber-600 text-white font-bold py-4 px-10 rounded-xl shadow-2xl hover:shadow-yellow-500/50 transition-all duration-300 transform hover:scale-105",
data: { turbo_frame: "modal" } do %>
<svg class="w-6 h-6 transition-transform group-hover:rotate-90 duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
Create First Strategy
<% end %>
</div>
</div>
<% end %>
</div>
18 changes: 18 additions & 0 deletions app/views/strategies/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%= turbo_frame_tag "modal" do %>
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div class="sticky top-0 bg-gradient-to-r from-yellow-500 to-amber-500 px-8 py-6 flex items-center justify-between">
<h2 class="text-3xl font-black text-white">New Strategy</h2>
<%= link_to strategies_path, class: "text-white hover:bg-white/20 rounded-lg p-2 transition-colors", data: { turbo_frame: "_top" } do %>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<% end %>
</div>

<div class="px-8 py-6">
<%= render "form", strategy: @strategy %>
</div>
</div>
</div>
<% end %>
11 changes: 11 additions & 0 deletions app/views/strategies/update.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<%= turbo_stream.replace dom_id(@strategy) do %>
<%= render @strategy %>
<% end %>

<%= turbo_stream.update "modal", "" %>

<%= turbo_stream.update "flash" do %>
<div class="bg-blue-500 text-white px-6 py-4 rounded-md shadow-lg mb-4 animate-fade-in">
✓ Strategy updated successfully!
</div>
<% end %>
Loading
Loading