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
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ruby.rubocop.useBundler": true,
"ruby.rubocop.commandPath": "bin/rubocop"
}
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ group :development, :test do
gem "pry-rails"

# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
gem "brakeman", "~> 7.1.2", require: false
gem "brakeman", "~> 8.0.2", require: false

# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ GEM
bindex (0.8.1)
bootsnap (1.18.6)
msgpack (~> 1.2)
brakeman (7.1.2)
brakeman (8.0.2)
racc
builder (3.3.0)
capybara (3.40.0)
Expand Down Expand Up @@ -378,7 +378,7 @@ PLATFORMS

DEPENDENCIES
bootsnap
brakeman (~> 7.1.2)
brakeman (~> 8.0.2)
capybara
csv
debug
Expand Down
20 changes: 2 additions & 18 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,31 +173,15 @@ th a:hover {
}

/* Status styling */
.status-submitted {
.status-applied {
color: #0d6efd;
}
.status-under_review {
color: #fd7e14;
}
.status-phone_screen {
color: #20c997;
}
.status-interview {
.status-interviewing {
color: #6f42c1;
}
.status-responded {
color: #198754;
}
.status-offer {
color: #198754;
font-weight: bold;
}
.status-closed {
color: #dc3545;
}
.status-withdrawn {
color: #6c757d;
}

/* Dashboard Cards */
.dashboard-cards {
Expand Down
4 changes: 0 additions & 4 deletions app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
class DashboardController < ApplicationController
def index
@total_potentials = Opportunity.where(application_date: nil).count
@total_resumes = Opportunity.where.not(application_date: nil).count
@total_responses = Opportunity.where(status: "responded").count
@total_interviews = Opportunity.where(status: "interview").count
@total_offers = Opportunity.where(status: "offer").count
@total_assessed = Opportunity.count

# Tech stack analytics
Expand Down
15 changes: 15 additions & 0 deletions app/models/concerns/role_types/other.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module RoleTypes
module Other
extend ActiveSupport::Concern

# Other role metadata structure can be anything that doesn't fit the defined roles.

included do
validate :validate_other_role_metadata, if: -> { role_type == "other" }
end

def validate_other_role_metadata
# No required metadata for "other"
end
end
end
32 changes: 32 additions & 0 deletions app/models/concerns/role_types/support_engineer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module RoleTypes
module SupportEngineer
extend ActiveSupport::Concern

# Support Engineer specific metadata structure:
# {
# support_tier: "l2", # l1, l2, l3
# on_call: true,
# ticket_volume: "high", # low, medium, high
# escalation_path: "engineering", # engineering, product, customer_success
# support_tools: ["zendesk", "jira"],
# shift_type: "rotating" # fixed, rotating, follow_the_sun
# }

included do
validate :validate_support_engineer_metadata, if: -> { role_type == "support_engineer" }
end

def validate_support_engineer_metadata
# Add any specific validations for support engineer roles
end

def support_summary
return "Not specified" unless role_metadata["support_tier"].present?

tier = role_metadata["support_tier"].upcase
on_call = role_metadata["on_call"] ? "On-call" : nil

[ "Tier #{tier}", on_call ].compact.join(" • ")
end
end
end
14 changes: 13 additions & 1 deletion app/models/opportunity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class Opportunity < ApplicationRecord
include RoleTypes::SalesEngineer
include RoleTypes::SolutionsEngineer
include RoleTypes::ProductManager
include RoleTypes::SupportEngineer
include RoleTypes::Other

belongs_to :company
has_many :opportunity_technologies, dependent: :destroy
Expand All @@ -19,7 +21,9 @@ class Opportunity < ApplicationRecord
"software_engineer" => "Software Engineer",
"sales_engineer" => "Sales Engineer",
"solutions_engineer" => "Solutions Engineer",
"product_manager" => "Product Manager"
"product_manager" => "Product Manager",
"support_engineer" => "Support Engineer",
"other" => "Other"
}.freeze

validates :role_type, presence: true, inclusion: { in: ROLE_TYPES.keys }
Expand Down Expand Up @@ -48,6 +52,14 @@ def product_manager?
role_type == "product_manager"
end

def support_engineer?
role_type == "support_engineer"
end

def other?
role_type == "other"
end

# Get/Set role metadata with indifferent access
def metadata
@metadata ||= (role_metadata || {}).with_indifferent_access
Expand Down
29 changes: 19 additions & 10 deletions app/views/dashboard/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,31 @@
</div>
</div>
<% end %>
</div>

<h2 style="margin-top: 3rem; margin-bottom: 1rem; color: var(--text-secondary);">Applications by Week</h2>
<div class="dashboard-cards weekly-cards">
<% @weekly_data.each_with_index do |week, index| %>
<%= link_to opportunities_path(start_date: week[:start_date], end_date: week[:end_date]), class: "card card-week clickable-week-card", style: "text-decoration: none; color: inherit;" do %>
<div class="dashboard-cards" style="flex: 1; min-width: 320px;">
<div class="card">
<div class="card-header">
<h3><%= week[:label] %></h3>
<h3>Applications by Week</h3>
<div class="card-icon">📅</div>
</div>
<div class="card-body">
<div class="card-number"><%= week[:count] %></div>
<p class="card-subtitle">applications</p>
<div class="dashboard-cards weekly-cards" style="margin: 0;">
<% @weekly_data.each_with_index do |week, index| %>
<%= link_to opportunities_path(start_date: week[:start_date], end_date: week[:end_date]), class: "card card-week clickable-week-card", style: "text-decoration: none; color: inherit;" do %>
<div class="card-header">
<h3><%= week[:label] %></h3>
<div class="card-icon">📅</div>
</div>
<div class="card-body">
<div class="card-number"><%= week[:count] %></div>
<p class="card-subtitle">applications</p>
</div>
<% end %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
</div>

<div style="display: flex; gap: 2rem; flex-wrap: wrap; margin-top: 2rem;">
Expand Down
21 changes: 13 additions & 8 deletions app/views/opportunities/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,9 @@
<%= form.label :status, style: "display: block" %>
<%= form.select :status,
options_for_select([
["Applied", "submitted"],
["Under Review", "under_review"],
["Phone Screen", "phone_screen"],
["Interview Scheduled", "interview"],
["Responded", "responded"],
["Offer Received", "offer"],
["Closed", "closed"],
["Withdrawn", "withdrawn"]
["Applied", "applied"],
["Interviewing", "interviewing"],
["Closed", "closed"]
], opportunity.status),
{ include_blank: "Select status" },
{ style: "display: block; width: 250px; height: 30px;" } %>
Expand Down Expand Up @@ -115,6 +110,16 @@
<div data-role-type-target="section" data-role="product_manager" style="<%= opportunity.role_type == 'product_manager' ? '' : 'display: none;' %>">
<%= render "product_manager_fields", opportunity: opportunity %>
</div>

<!-- Support Engineer Fields -->
<div data-role-type-target="section" data-role="support_engineer" style="<%= opportunity.role_type == 'support_engineer' ? '' : 'display: none;' %>">
<%= render "support_engineer_fields", opportunity: opportunity %>
</div>

<!-- Other Fields -->
<div data-role-type-target="section" data-role="other" style="<%= opportunity.role_type == 'other' ? '' : 'display: none;' %>">
<%= render "other_fields", opportunity: opportunity %>
</div>
</div>

<div>
Expand Down
6 changes: 5 additions & 1 deletion app/views/opportunities/_opportunity.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"#17a2b8" # Teal
when "product_manager"
"#6f42c1" # Purple
when "support_engineer"
"#fd7e14" # Orange
when "other"
"#343a40" # Dark
else
"#6c757d" # Default gray
end
Expand All @@ -22,7 +26,7 @@
font-size: 11px;
white-space: nowrap;
display: inline-block;
width: 80px;
width: 95px;
text-align: center;
">
<%= opportunity.role_type_label.gsub("Engineer", "Eng").gsub("Manager", "Mgr") %>
Expand Down
20 changes: 19 additions & 1 deletion app/views/opportunities/_opportunity_details.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@
<div class="opportunity-details">
<p>
<strong>Role Type:</strong>
<span class="badge" style="background-color: #6c757d; color: white; padding: 5px 10px; border-radius: 4px; font-size: 14px;">
<%
badge_color = case opportunity.role_type
when "software_engineer"
"#6c757d"
when "sales_engineer"
"#28a745"
when "solutions_engineer"
"#17a2b8"
when "product_manager"
"#6f42c1"
when "support_engineer"
"#fd7e14"
when "other"
"#343a40"
else
"#6c757d"
end
%>
<span class="badge" style="background-color: <%= badge_color %>; color: white; padding: 5px 10px; border-radius: 4px; font-size: 14px;">
<%= opportunity.role_type_label %>
</span>
</p>
Expand Down
5 changes: 5 additions & 0 deletions app/views/opportunities/_other_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div style="margin-top: 10px; padding: 10px; background-color: var(--bg-secondary, #f8f9fa); border-radius: 4px;">
<p style="margin: 0; color: var(--text-secondary, #666); font-size: 13px;">
No role-specific fields for "Other".
</p>
</div>
5 changes: 5 additions & 0 deletions app/views/opportunities/_support_engineer_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div style="margin-top: 10px; padding: 10px; background-color: var(--bg-secondary, #f8f9fa); border-radius: 4px;">
<p style="margin: 0; color: var(--text-secondary, #666); font-size: 13px;">
No additional fields for Support Engineer roles yet.
</p>
</div>
12 changes: 12 additions & 0 deletions app/views/opportunities/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@
#{ params[:role_type] == 'product_manager' ? 'background-color: #6f42c1; color: white;' : 'background-color: white; color: #333; border: 1px solid #dee2e6;' }",
onmouseover: "if (!this.style.backgroundColor.includes('rgb(111, 66, 193)')) { this.style.backgroundColor='#e9ecef'; }",
onmouseout: "if (!this.style.backgroundColor.includes('rgb(111, 66, 193)')) { this.style.backgroundColor='white'; }" %>

<%= link_to "Support Engineer", opportunities_path(role_type: "support_engineer"),
style: "padding: 6px 16px; border-radius: 4px; text-decoration: none; font-size: 14px; transition: all 0.2s; width: 160px; text-align: center;
#{ params[:role_type] == 'support_engineer' ? 'background-color: #fd7e14; color: white;' : 'background-color: white; color: #333; border: 1px solid #dee2e6;' }",
onmouseover: "if (!this.style.backgroundColor.includes('rgb(253, 126, 20)')) { this.style.backgroundColor='#e9ecef'; }",
onmouseout: "if (!this.style.backgroundColor.includes('rgb(253, 126, 20)')) { this.style.backgroundColor='white'; }" %>

<%= link_to "Other", opportunities_path(role_type: "other"),
style: "padding: 6px 16px; border-radius: 4px; text-decoration: none; font-size: 14px; transition: all 0.2s; width: 160px; text-align: center;
#{ params[:role_type] == 'other' ? 'background-color: #343a40; color: white;' : 'background-color: white; color: #333; border: 1px solid #dee2e6;' }",
onmouseover: "if (!this.style.backgroundColor.includes('rgb(52, 58, 64)')) { this.style.backgroundColor='#e9ecef'; }",
onmouseout: "if (!this.style.backgroundColor.includes('rgb(52, 58, 64)')) { this.style.backgroundColor='white'; }" %>
</div>
<% if @role_filter %>
<span style="margin-left: 1rem; color: var(--text-muted); font-size: 0.9rem;">
Expand Down
21 changes: 21 additions & 0 deletions db/migrate/20260203130000_normalize_opportunity_statuses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class NormalizeOpportunityStatuses < ActiveRecord::Migration[8.0]
def up
execute <<~SQL
UPDATE opportunities
SET status = 'applied'
WHERE status = 'submitted';

UPDATE opportunities
SET status = 'interviewing'
WHERE status IN ('under_review', 'phone_screen', 'interview', 'responded', 'offer');

UPDATE opportunities
SET status = 'closed'
WHERE status = 'withdrawn';
SQL
end

def down
raise ActiveRecord::IrreversibleMigration
end
end
2 changes: 1 addition & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions lib/tasks/update_proptech_industry.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace :companies do
desc "Update Real Estate related industries to PropTech"
task update_proptech_industry: :environment do
patterns = [
"Real Estate",
"Real Estate Technology",
"Real Estate SaaS"
]

companies_to_update = Company.where(
patterns.map { |pattern| "industry ILIKE ?" }.join(" OR "),
*patterns.map { |pattern| "%#{pattern}%" }
)

count = companies_to_update.count

if count.zero?
puts "No companies found with Real Estate related industries."
else
puts "Found #{count} #{"company".pluralize(count)} to update:"
companies_to_update.each do |company|
puts " - #{company.name}: '#{company.industry}' -> 'PropTech'"
end

print "\nProceed with update? (y/n): "
response = STDIN.gets.chomp.downcase

if response == "y"
updated = companies_to_update.update_all(industry: "PropTech")
puts "\n✓ Successfully updated #{updated} #{"company".pluralize(updated)} to PropTech"
else
puts "\n✗ Update cancelled"
end
end
end
end
2 changes: 1 addition & 1 deletion test/system/opportunities_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class OpportunitiesTest < ApplicationSystemTestCase
select @opportunity.company.name, from: "Company"
fill_in "Notes", with: @opportunity.notes
fill_in "Position title", with: @opportunity.position_title
select "Under Review", from: "Status"
select "Interviewing", from: "Status"
check "Python"
select "Indeed", from: "Source"
click_on "Update Opportunity"
Expand Down