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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ gem "phlex-rails", "~> 1.2"
gem "devise", "~> 4.9"
gem "will_paginate", "~> 4.0"
gem "validate_url", "~> 1.0"
gem "rexml", ">= 3.3.2"

group :development, :test do
gem "debug", "~> 1.9", ">= 1.9.2", platforms: %i[mri windows]
Expand Down
5 changes: 3 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ GEM
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.8)
strscan (>= 3.0.9)
rexml (3.3.2)
strscan
rubocop (1.64.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
Expand Down Expand Up @@ -366,6 +366,7 @@ DEPENDENCIES
phlex-rails (~> 1.2)
puma (~> 6.4, >= 6.4.2)
rails (~> 7.1, >= 7.1.3.4)
rexml (>= 3.3.2)
rubocop (~> 1.63, >= 1.63.5)
rubocop-factory_bot (~> 2.25, >= 2.25.1)
rubocop-minitest (~> 0.35.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

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.

<img alt="UI Screenshot" src="https://github.com/user-attachments/assets/1b4ec900-a670-4220-bf01-1df1cfba5914"/>
<img alt="UI Screenshot" src="https://github.com/user-attachments/assets/ab65ca79-4530-4730-b6b3-d839cfdf6398"/>

## Key Features

Expand Down
66 changes: 42 additions & 24 deletions app/controllers/job_applications_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class JobApplicationsController < ApplicationController
before_action :set_job_application, only: [:create, :update, :destroy]
include ActionView::RecordIdentifier
before_action :set_job_application, only: [:edit, :update, :destroy]

def index
@job_applications = filter_and_sort_job_applications
Expand All @@ -22,53 +23,61 @@ def index

def new
@job_application = JobApplication.new
respond_to do |format|
format.html
format.turbo_stream { render turbo_stream: turbo_stream.replace("new_job_application", partial: "form", locals: {job_application: @job_application, title: "New"}) }
end
end

def edit
respond_to do |format|
format.html
format.turbo_stream { render turbo_stream: turbo_stream.replace(dom_id(@job_application), partial: "form", locals: {job_application: @job_application, title: "Edit"}) }
end
end

def create
@job_application = JobApplication.new(job_application_params)

respond_to do |format|
if @job_application.save
format.html { redirect_to job_applications_path, notice: "Job application was successfully created." }
format.turbo_stream {
flash.now[:notice] = "Job application was successfully created."
render turbo_stream: [
turbo_stream.prepend("job_applications", partial: "job_application", locals: {job_application: @job_application}),
turbo_stream.update("flash_messages", partial: "flash_messages"),
turbo_stream.replace("new_job_application", partial: "form", locals: {job_application: JobApplication.new})
]
}
format.html { redirect_to root_path, success: "Job application was successfully created." }
format.turbo_stream do
render turbo_stream: turbo_stream.redirect_advanced(root_path)
flash[:success] = "Job application was successfully created."
end
else
format.html { render :new, status: :unprocessable_entity }
format.turbo_stream { render turbo_stream: turbo_stream.replace(@job_application, partial: "form", locals: {job_application: @job_application}) }
format.turbo_stream do
render turbo_stream: [
turbo_stream.replace("new_job_application", partial: "form", locals: {job_application: @job_application, title: "New"}),
turbo_stream.update("flash_messages", partial: "flash_messages")
]
end
end
end
end

def update
respond_to do |format|
if @job_application.update(job_application_params)
format.html { redirect_to job_applications_path, notice: "Job application was successfully updated." }
format.turbo_stream {
flash.now[:notice] = "Job application was successfully updated."
render turbo_stream: [
turbo_stream.replace(@job_application, partial: "job_application", locals: {job_application: @job_application}),
turbo_stream.update("flash_messages", partial: "flash_messages")
]
}
format.html { redirect_to root_path, success: "Job application was successfully updated." }
format.turbo_stream do
render turbo_stream: turbo_stream.redirect_advanced(root_path)
flash[:success] = "Job application was successfully updated."
end
else
format.html { render :edit, status: :unprocessable_entity }
format.turbo_stream { render turbo_stream: turbo_stream.replace(@job_application, partial: "form", locals: {job_application: @job_application}) }
end
end
end

def destroy
@job_application.destroy
respond_to do |format|
format.html { redirect_to job_applications_url, notice: "Job application was successfully deleted." }
format.html { redirect_to root_path, success: "Job application was successfully deleted." }
format.turbo_stream {
flash.now[:notice] = "Job application was successfully deleted."
flash.now[:success] = "Job application was successfully deleted."
render turbo_stream: [
turbo_stream.remove(@job_application),
turbo_stream.update("job_application_count", JobApplication.count),
Expand All @@ -82,6 +91,8 @@ def destroy

def set_job_application
@job_application = JobApplication.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to root_path, alert: "Job application not found."
end

def job_application_params
Expand All @@ -97,11 +108,18 @@ def filter_and_sort_job_applications

sort_column = sort_column(params[:sort])
sort_direction = sort_direction(params[:direction])
job_applications.order(sort_column => sort_direction)

if sort_column == "created_at" || params[:sort].blank?
# If sorting by created_at or no sorting specified, always use desc order
job_applications.order(created_at: :desc)
else
# For other columns, use the specified sort direction
job_applications.order(sort_column => sort_direction)
end
end

def sort_column(column)
%w[date_applied company_name position_title].include?(column) ? column : "date_applied"
%w[date_applied company_name position_title created_at].include?(column) ? column : "created_at"
end

def sort_direction(direction)
Expand Down
14 changes: 12 additions & 2 deletions app/frontend/controllers/flash_message_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ export default class extends Controller {
show() {
this.element.classList.remove('hidden');
setTimeout(() => {
this.element.classList.add('hidden');
}, 5000);
this.fadeOut();
}, 2000);
}

fadeOut() {
const flashMessage = this.element.querySelector('.flash-message');
if (flashMessage) {
flashMessage.classList.add('fade-out');
setTimeout(() => {
this.element.classList.add('hidden');
}, 500);
}
}
}
10 changes: 8 additions & 2 deletions app/frontend/entrypoints/application.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "~/controllers";
import "@hotwired/turbo-rails";
import { Turbo } from "@hotwired/turbo-rails"
// To see this message, add the following to the `<head>` section in your
// views/layouts/application.html.erb
//
Expand All @@ -20,7 +20,7 @@ console.log('Visit the guide for more information: ', 'https://vite-ruby.netlify

Turbo.start();

document.addEventListener("turbo:load", function () {
document.addEventListener("turbo:load", () => {
console.log("turbo:load");
});
//
Expand All @@ -32,3 +32,9 @@ document.addEventListener("turbo:load", function () {

// Example: Import a stylesheet in app/frontend/index.css
// import '~/index.css'

Turbo.StreamActions.redirect_advanced = function () {
const url = this.getAttribute('url') || '/'
// Turbo.visit(url, { frame: '_top', action: 'advance' })
Turbo.visit(url)
}
8 changes: 8 additions & 0 deletions app/frontend/stylesheets/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ body {
background-position: center;
background-attachment: fixed;
}

td {
font-size: 12px !important;
}

th {
font-size: 12px !important;
}
34 changes: 28 additions & 6 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
module ApplicationHelper
def flash_class(type)
base_class = "border-l-4 p-4 mb-4 rounded-md"
case type.to_sym
when :notice then "bg-blue-100 border-blue-500 text-blue-700"
when :success then "bg-green-100 border-green-500 text-green-700"
when :error then "bg-red-100 border-red-500 text-red-700"
when :alert then "bg-yellow-100 border-yellow-500 text-yellow-700"
else "bg-gray-100 border-gray-500 text-gray-700"
end + " border-l-4 p-4 mb-4"
when :notice
"bg-blue-100 border-blue-500 text-blue-700 #{base_class}"
when :success
"bg-green-100 border-green-500 text-green-700 #{base_class}"
when :error
"bg-red-100 border-red-500 text-red-700 #{base_class}"
when :alert
"bg-yellow-100 border-yellow-500 text-yellow-700 #{base_class}"
else
"bg-gray-100 border-gray-500 text-gray-700 #{base_class}"
end
end

def progress_bar_class(type)
base_class = "rounded-md"
case type.to_sym
when :notice
"bg-blue-500 #{base_class}"
when :success
"bg-green-500 #{base_class}"
when :error
"bg-red-500 #{base_class}"
when :alert
"bg-yellow-500 #{base_class}"
else
"bg-gray-500 #{base_class}"
end
end

def sort_link_to(name, column)
Expand Down
8 changes: 8 additions & 0 deletions app/helpers/turbo_stream_actions_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module TurboStreamActionsHelper
# render turbo_stream: turbo_stream.redirect_advanced(projects_path)
def redirect_advanced(url)
turbo_stream_action_tag :redirect_advanced, url: url
end
end

Turbo::Streams::TagBuilder.prepend(TurboStreamActionsHelper)
34 changes: 32 additions & 2 deletions app/views/application/_flash_messages.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
<div data-controller="flash-message" class="hidden">
<style>
@keyframes flash-progress {
from {
width: 100%;
left: 0;
}
to {
width: 0;
left: 0;
}
}

@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

.flash-progress-bar {
animation: flash-progress 2s linear forwards;
}

.flash-message.fade-out {
animation: fade-out 0.5s forwards;
}
</style>
<div data-controller="flash-message" class="hidden relative">
<% flash.each do |type, message| %>
<div class="<%= flash_class(type) %>" role="alert">
<div class="<%= flash_class(type) %> flash-message relative" role="alert">
<div class="flash-progress-bar absolute top-0 left-0 h-1 w-full <%= progress_bar_class(type) %>"></div>
<%= message %>
</div>
<% end %>
Expand Down
Loading