Skip to content

Commit 83d9f1e

Browse files
authored
Merge pull request tgaeta#3 from tgaeta/add-migrations-to-job-application
Add Migrations for Status, Unemployment, and Include Tests
2 parents f539c04 + 8eaea99 commit 83d9f1e

21 files changed

+435
-90
lines changed

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ gem "validate_url", "~> 1.0"
1919
gem "rexml", ">= 3.3.2"
2020

2121
group :development, :test do
22+
gem "byebug", "~> 11.1", ">= 11.1.3"
23+
gem "database_cleaner-active_record", "1.8.0.beta"
2224
gem "debug", "~> 1.9", ">= 1.9.2", platforms: %i[mri windows]
2325
gem "dotenv", "~> 3.1", ">= 3.1.2"
2426
gem "factory_bot_rails", "~> 6.4", ">= 6.4.3"
2527
gem "faker", "~> 3.4"
28+
gem "mocha", "~> 1.2", ">= 1.2.1"
29+
gem "rails-controller-testing", "~> 0.0.3"
2630
gem "rubocop-rails-omakase", "~> 1.0", require: false, group: [:development]
2731
gem "minitest-reporters", "~> 1.6", ">= 1.6.1"
2832
end

Gemfile.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,16 @@ GEM
9898
bundler-audit (0.9.1)
9999
bundler (>= 1.2.0, < 3)
100100
thor (~> 1.0)
101+
byebug (11.1.3)
101102
childprocess (5.0.0)
102103
colorize (1.1.0)
103104
concurrent-ruby (1.2.3)
104105
connection_pool (2.4.1)
105106
crass (1.0.6)
107+
database_cleaner (1.8.5)
108+
database_cleaner-active_record (1.8.0.beta)
109+
activerecord
110+
database_cleaner (~> 1.8.0.beta)
106111
date (3.3.4)
107112
debug (1.9.2)
108113
irb (~> 1.10)
@@ -171,6 +176,7 @@ GEM
171176
builder
172177
minitest (>= 5.0)
173178
ruby-progressbar
179+
mocha (1.16.1)
174180
msgpack (1.7.2)
175181
mutex_m (0.2.0)
176182
net-imap (0.4.13)
@@ -236,6 +242,8 @@ GEM
236242
activesupport (= 7.1.3.4)
237243
bundler (>= 1.15.0)
238244
railties (= 7.1.3.4)
245+
rails-controller-testing (0.0.3)
246+
rails (>= 4.2)
239247
rails-dom-testing (2.2.0)
240248
activesupport (>= 5.0.0)
241249
minitest
@@ -351,7 +359,9 @@ DEPENDENCIES
351359
bootsnap (~> 1.18, >= 1.18.3)
352360
brakeman
353361
bundler-audit (~> 0.9.1)
362+
byebug (~> 11.1, >= 11.1.3)
354363
colorize (~> 1.1)
364+
database_cleaner-active_record (= 1.8.0.beta)
355365
debug (~> 1.9, >= 1.9.2)
356366
devise (~> 4.9)
357367
dotenv (~> 3.1, >= 3.1.2)
@@ -362,10 +372,12 @@ DEPENDENCIES
362372
jbuilder (~> 2.12)
363373
letter_opener (~> 1.10)
364374
minitest-reporters (~> 1.6, >= 1.6.1)
375+
mocha (~> 1.2, >= 1.2.1)
365376
pg (~> 1.5, >= 1.5.6)
366377
phlex-rails (~> 1.2)
367378
puma (~> 6.4, >= 6.4.2)
368379
rails (~> 7.1, >= 7.1.3.4)
380+
rails-controller-testing (~> 0.0.3)
369381
rexml (>= 3.3.2)
370382
rubocop (~> 1.63, >= 1.63.5)
371383
rubocop-factory_bot (~> 2.25, >= 2.25.1)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
Job Tracker is a simple, powerful, and user-friendly web application designed to help job seekers efficiently manage their job search process. Built with Ruby on Rails and enhanced with modern web technologies, this tool streamlines the often overwhelming task of tracking multiple job applications.
2222

23-
<img alt="UI Screenshot" src="https://github.com/user-attachments/assets/ab65ca79-4530-4730-b6b3-d839cfdf6398"/>
23+
<img alt="UI Screenshot" src="https://github.com/user-attachments/assets/8153c979-2bc9-437b-965d-c838506d5036"/>
2424

2525
## Key Features
2626

app/controllers/job_applications_controller.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def set_job_application
9696
end
9797

9898
def job_application_params
99-
params.require(:job_application).permit(:date_applied, :company_name, :method_of_contact, :email_address, :point_of_contact, :website_link, :position_type, :position_title)
99+
params.require(:job_application).permit(:date_applied, :company_name, :method_of_contact, :email_address, :point_of_contact, :website_link, :position_type, :position_title, :claimed_for_unemployment, :status)
100100
end
101101

102102
def filter_and_sort_job_applications
@@ -105,6 +105,9 @@ def filter_and_sort_job_applications
105105
job_applications = job_applications.search(params[:search]) if params[:search].present?
106106
job_applications = job_applications.by_method_of_contact(params[:method_of_contact]) if params[:method_of_contact].present?
107107
job_applications = job_applications.by_position_type(params[:position_type]) if params[:position_type].present?
108+
job_applications = job_applications.claimed_for_unemployment if params[:claimed_for_unemployment] == "true"
109+
job_applications = job_applications.not_claimed_for_unemployment if params[:claimed_for_unemployment] == "false"
110+
job_applications = job_applications.by_status(params[:status]) if params[:status].present?
108111

109112
sort_column = sort_column(params[:sort])
110113
sort_direction = sort_direction(params[:direction])
@@ -119,7 +122,7 @@ def filter_and_sort_job_applications
119122
end
120123

121124
def sort_column(column)
122-
%w[date_applied company_name position_title created_at].include?(column) ? column : "created_at"
125+
%w[date_applied company_name position_title created_at claimed_for_unemployment status].include?(column) ? column : "created_at"
123126
end
124127

125128
def sort_direction(direction)

app/helpers/application_helper.rb

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,31 @@ def progress_bar_class(type)
3232
end
3333

3434
def sort_link_to(name, column)
35-
direction = (column.to_s == params[:sort] && params[:direction] == "asc") ? "desc" : "asc"
36-
link_to name,
37-
request.params.merge(sort: column, direction: direction),
38-
class: "text-gray-600 hover:text-gray-900",
35+
current_column = params[:sort]
36+
current_direction = params[:direction]
37+
38+
is_current_column = column.to_s == current_column
39+
next_direction = (is_current_column && current_direction == "asc") ? "desc" : "asc"
40+
41+
icon = if is_current_column
42+
(current_direction == "asc") ? "↑" : "↓"
43+
else
44+
""
45+
end
46+
47+
link_to request.params.merge(sort: column, direction: next_direction),
48+
class: "text-gray-600 hover:text-gray-900 inline-flex items-center",
3949
data: {
4050
turbo_frame: "job_applications_table",
4151
turbo_action: "replace"
42-
}
52+
} do
53+
content_tag(:span, class: "flex items-center") do
54+
safe_join([
55+
content_tag(:span, name, class: "mr-1"),
56+
content_tag(:span, icon, class: "sort-icon")
57+
])
58+
end
59+
end
4360
end
4461

4562
def safe_url(url)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
module JobApplicationsHelper
2+
def display_position_type(position_type)
3+
case position_type
4+
when "full_time" then "Full-time"
5+
when "part_time" then "Part-time"
6+
when "internship" then "Internship"
7+
else
8+
position_type
9+
end
10+
end
211
end

app/models/job_application.rb

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,50 @@
11
class JobApplication < ApplicationRecord
2+
attribute :status, :string
3+
24
validates :date_applied, :company_name, :method_of_contact, :position_type, :position_title, presence: true
35
validates :email_address, presence: true, if: -> { method_of_contact == "email" }
46
validates :point_of_contact, presence: true, if: -> { ["email", "phone", "recruiter", "other"].include?(method_of_contact) }
5-
validates :website_link, presence: true, if: -> { method_of_contact == "internet job application" }
7+
validates :website_link, presence: true, if: -> { method_of_contact == "internet_job_application" }
68
validates :website_link, url: {
79
allow_blank: true,
810
schemes: ["http", "https"],
911
no_local: true,
1012
public_suffix: true
1113
}
12-
enum method_of_contact: {email: "email", phone: "phone", internet_job_application: "internet job application", recruiter: "recruiter", other: "other"}
13-
enum position_type: {full_time: "full-time", part_time: "part-time", internship: "internship"}
14+
validates :claimed_for_unemployment, inclusion: {in: [true, false]}
15+
validates :status, inclusion: {in: %w[hired interviewing job_offer no_response not_hired]}
16+
17+
enum method_of_contact: {
18+
email: "email",
19+
internet_job_application: "internet job application",
20+
other: "other",
21+
phone: "phone",
22+
recruiter: "recruiter"
23+
}
24+
enum position_type: {
25+
full_time: "full_time",
26+
internship: "internship",
27+
part_time: "part_time"
28+
}
29+
enum status: {
30+
hired: "hired",
31+
interviewing: "interviewing",
32+
job_offer: "job offer",
33+
no_response: "no response",
34+
not_hired: "not hired"
35+
}
1436

15-
scope :search, ->(query) { where("company_name ILIKE ? OR position_title ILIKE ?", "%#{query}%", "%#{query}%") }
1637
scope :by_method_of_contact, ->(method) { where(method_of_contact: method) }
1738
scope :by_position_type, ->(type) { where(position_type: type) }
39+
scope :by_status, ->(status) { where(status: status) }
40+
scope :claimed_for_unemployment, -> { where(claimed_for_unemployment: true) }
41+
scope :not_claimed_for_unemployment, -> { where(claimed_for_unemployment: false) }
42+
scope :search, ->(query) {
43+
where("company_name ILIKE :query OR
44+
position_title ILIKE :query OR
45+
email_address ILIKE :query OR
46+
point_of_contact ILIKE :query OR
47+
website_link ILIKE :query OR
48+
status ILIKE :query", query: "%#{query}%")
49+
}
1850
end

app/views/job_applications/_form.html.erb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,22 @@
4444
</div>
4545
<div>
4646
<%= form.label :position_type, class: 'block text-sm font-medium text-gray-700 mb-2' %>
47-
<%= form.select :position_type, JobApplication.position_types.keys.map { |k| [k.humanize, k] }, { include_blank: 'Select a type' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
47+
<%= form.select :position_type, JobApplication.position_types.keys.map { |k| [display_position_type(k), k] }, { include_blank: 'Select a type' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
4848
</div>
4949
<div>
5050
<%= form.label :position_title, class: 'block text-sm font-medium text-gray-700 mb-2' %>
5151
<%= form.text_field :position_title, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
5252
</div>
53+
<div>
54+
<%= form.label :status, class: 'block text-sm font-medium text-gray-700 mb-2' %>
55+
<%= form.select :status, JobApplication.statuses.keys.map { |k| [k.humanize, k] }, { include_blank: 'Select a status' }, class: 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline' %>
56+
<div class="mt-8">
57+
<%= form.label :claimed_for_unemployment, class: 'flex items-center' do %>
58+
<%= form.check_box :claimed_for_unemployment, class: 'h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded mr-2' %>
59+
<span class="text-sm font-medium text-gray-700">Claimed for Unemployment</span>
60+
<% end %>
61+
</div>
62+
</div>
5363
</div>
5464
<div class="flex items-center justify-end mt-6">
5565
<%= link_to 'Cancel', root_path, class: 'bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mr-2', data: { turbo_frame: "_top" } %>

app/views/job_applications/_job_application.html.erb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<tr id="<%= dom_id(job_application) %>">
1+
<tr class="h-20" id="<%= dom_id(job_application) %>">
22
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
33
<%= job_application.date_applied.strftime('%m/%d/%Y') %>
44
</td>
@@ -9,17 +9,23 @@
99
<%= job_application.position_title %>
1010
</td>
1111
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
12-
<%= job_application.position_type.humanize %>
12+
<%= display_position_type(job_application.position_type) %>
1313
</td>
1414
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
1515
<%= job_application.method_of_contact.humanize %>
1616
</td>
1717
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
18-
<%= job_application.point_of_contact || '-' %>
18+
<%= job_application.point_of_contact.present? ? job_application.point_of_contact : '-' %>
1919
</td>
2020
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
2121
<%= job_application.email_address.present? ? job_application.email_address : '-' %>
2222
</td>
23+
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
24+
<%= job_application.claimed_for_unemployment ? '✔︎' : '-' %>
25+
</td>
26+
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
27+
<%= job_application.status.humanize %>
28+
</td>
2329
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
2430
<% if job_application.website_link.present? %>
2531
<%= link_to 'Visit',
Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,57 @@
11
<%= turbo_frame_tag "job_applications_table" do %>
22
<div class="overflow-x-auto bg-white shadow-md rounded-lg">
33
<% if @job_applications.any? %>
4-
<table class="min-w-full leading-normal">
5-
<thead class="text-xs">
4+
<table class="w-full">
5+
<thead class="text-xs bg-gray-100 text-left font-semibold text-gray-600 w-8 border-b-2">
66
<tr>
7-
<tr>
8-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
9-
<%= sort_link_to 'Date Applied', 'date_applied' %>
10-
</th>
11-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
12-
<%= sort_link_to 'Company Name', 'company_name' %>
13-
</th>
14-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
15-
<%= sort_link_to 'Position', 'position_title' %>
16-
</th>
17-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
18-
Type
19-
</th>
20-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
21-
Contact Method
22-
</th>
23-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
24-
P.o.C.
25-
</th>
26-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
27-
Email
28-
</th>
29-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
30-
Website
31-
</th>
32-
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left font-semibold text-gray-600 tracking-wider">
33-
Actions
34-
</th>
35-
</tr>
36-
</thead>
37-
<tbody id="job_applications">
38-
<%= render partial: 'job_application', collection: job_applications %>
39-
</tbody>
40-
</table>
41-
<% else %>
42-
<div class="text-center py-10">
43-
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
44-
<path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
45-
</svg>
46-
<p class="mt-1 text-sm text-gray-500">
47-
<%= "No results match your search criteria." if params[:search].present? || params[:method_of_contact].present? || params[:position_type].present? %>
48-
</p>
49-
</div>
50-
<% end %>
51-
</div>
52-
<% end %>
7+
<th class="px-5 py-3 w-8">
8+
<%= sort_link_to 'Applied', 'date_applied' %>
9+
</th>
10+
<th class="px-5 py-3 w-48">
11+
<%= sort_link_to 'Company', 'company_name' %>
12+
</th>
13+
<th class="px-5 py-3 w-48">
14+
<%= sort_link_to 'Position', 'position_title' %>
15+
</th>
16+
<th class="px-5 py-3">
17+
Type
18+
</th>
19+
<th class="px-5 py-3 w-48">
20+
Contact Method
21+
</th>
22+
<th class="px-5 py-3 w-48">
23+
P.o.C.
24+
</th>
25+
<th class="px-5 py-3 w-96">
26+
Email
27+
</th>
28+
<th class="px-5 py-3">
29+
<%= sort_link_to 'Claimed', 'claimed_for_unemployment' %>
30+
</th>
31+
<th class="px-5 py-3">
32+
<%= sort_link_to 'Status', 'status' %>
33+
</th>
34+
<th class="px-5 py-3">
35+
Website
36+
</th>
37+
<th class="px-5 py-3">
38+
Actions
39+
</th>
40+
</tr>
41+
</thead>
42+
<tbody id="job_applications">
43+
<%= render partial: 'job_application', collection: job_applications %>
44+
</tbody>
45+
</table>
46+
<% else %>
47+
<div class="text-center py-10">
48+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
49+
<path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
50+
</svg>
51+
<p class="mt-1 text-sm text-gray-500">
52+
<%= "No results match your search criteria." if params[:search].present? || params[:method_of_contact].present? || params[:position_type].present? %>
53+
</p>
54+
</div>
55+
<% end %>
56+
</div>
57+
<% end %>

0 commit comments

Comments
 (0)