diff --git a/Gemfile b/Gemfile index 0f3b34e..1f83dd0 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,9 @@ gem "appsignal", "~> 4.8", ">= 4.8.4" # honeybadger gem "honeybadger", "~> 6.6" +# rack-cors +gem "rack-cors", ">= 3.0" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" diff --git a/Gemfile.lock b/Gemfile.lock index bd9e366..e8c42f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -247,6 +247,9 @@ GEM raabro (1.4.0) racc (1.8.1) rack (3.2.3) + rack-cors (3.0.0) + logger + rack (>= 3.0.14) rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) @@ -463,6 +466,7 @@ DEPENDENCIES propshaft pry-rails puma (>= 5.0) + rack-cors (>= 3.0) rails (~> 8.0.3) rspec-rails rubocop-rails-omakase diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 980cbce..daf0402 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -230,30 +230,7 @@ th a:hover { @media (min-width: 1024px) { .dashboard-kpi-cards { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .dashboard-kpi-cards .funnel-card:nth-child(-n+2)::after { - content: ""; - position: absolute; - top: 50%; - right: -0.45rem; - width: 0.9rem; - border-top: 2px dashed var(--border-color); - transform: translateY(-50%); - z-index: 2; - } - - .dashboard-kpi-cards .funnel-card:nth-child(-n+2)::before { - content: ""; - position: absolute; - top: 50%; - right: -0.62rem; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 7px solid var(--border-color); - transform: translateY(-50%); - z-index: 2; + grid-template-columns: repeat(3, minmax(0, 1fr)); } } diff --git a/app/controllers/api/v1/opportunities_controller.rb b/app/controllers/api/v1/opportunities_controller.rb new file mode 100644 index 0000000..3c63bdc --- /dev/null +++ b/app/controllers/api/v1/opportunities_controller.rb @@ -0,0 +1,29 @@ +class Api::V1::OpportunitiesController < ApplicationController + def index + limit = params.fetch(:limit, 20).to_i + limit = [ [ limit, 1 ].max, 100 ].min + + scope = current_or_demo_user + .opportunities + .includes(:company) + .order(id: :desc) + + if params[:cursor].present? + scope = scope.where("id < ?", params[:cursor].to_i) + end + + rows = scope.limit(limit + 1).to_a + has_more = rows.length > limit + data = has_more ? rows.first(limit) : rows + next_cursor = has_more ? data.last.id : nil + + render json: { + data: data, + meta: { + limit: limit, + has_more: has_more, + next_cursor: next_cursor + } + } + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 96aa74f..d901352 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -5,7 +5,6 @@ def index @total_resumes = submitted_opportunities.count @total_open_applications = opportunities.where(status: %w[applied interviewing]).count - @total_assessed = opportunities.count # Count interview processes, not interview rounds: 1 per submitted opportunity that has interviews. @total_interviews = submitted_opportunities.joins(:interview_sessions).distinct.count @interview_conversion_rate = if @total_resumes.positive? diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index a94ddfc..f12f95b 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,2 +1,76 @@ module DashboardHelper + def pie_chart_svg(data, size: 200, colors: nil) + # data should be a hash of {label => value} or array of {name, value} + values = data.is_a?(Hash) ? data.values : data.map { |d| d[:value] || d[:count] } + total = values.sum.to_f + + # Enhanced color palette with yellow, green, and light blue for better contrast + default_colors = [ "#6366f1", "#22c55e", "#eab308", "#60a5fa", "#f43f5e", "#ec4899", "#f97316" ] + colors ||= default_colors + + svg_data = [] + current_angle = -90 + + data_array = data.is_a?(Hash) ? data.to_a : data + + data_array.each_with_index do |item, index| + label = data.is_a?(Hash) ? item[0] : item[:name] + value = data.is_a?(Hash) ? item[1] : (item[:value] || item[:count]) + percentage = (value.to_f / total * 100).round(1) + + # Calculate angle + angle = percentage * 3.6 + start_angle = current_angle + end_angle = current_angle + angle + + # Convert to radians + start_rad = (start_angle * Math::PI / 180) + end_rad = (end_angle * Math::PI / 180) + + # Calculate coordinates + radius = size / 2 - 10 + cx = size / 2 + cy = size / 2 + + x1 = cx + radius * Math.cos(start_rad) + y1 = cy + radius * Math.sin(start_rad) + x2 = cx + radius * Math.cos(end_rad) + y2 = cy + radius * Math.sin(end_rad) + + large_arc = angle > 180 ? 1 : 0 + + color = colors[index % colors.length] + + # SVG path for pie slice + path_data = "M #{cx},#{cy} L #{x1},#{y1} A #{radius},#{radius} 0 #{large_arc},1 #{x2},#{y2} Z" + svg_data << { + path: path_data, + color: color, + label: label, + value: value, + percentage: percentage + } + + current_angle = end_angle + end + + # Generate SVG + svg = "" + + # Return SVG and legend + legend_html = "
Total opportunities tracked
-