Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds client and project management for clients. #64

Merged
merged 12 commits into from
Feb 6, 2024
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ gem "view_component"

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

gem 'dotenv-rails', groups: [:development, :test]
gem "faker"

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
Expand All @@ -46,6 +44,7 @@ group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ]
gem "bullet"
gem 'dotenv-rails'
end

group :development do
Expand All @@ -70,4 +69,5 @@ group :test do
gem "timecop"
gem "webmock"
gem "vcr"
gem 'rails-controller-testing'
end
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ GEM
activesupport (= 7.1.2)
bundler (>= 1.15.0)
railties (= 7.1.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
Expand Down Expand Up @@ -345,6 +349,7 @@ DEPENDENCIES
puma (>= 5.0)
rack-cors
rails (= 7.1.2)
rails-controller-testing
rspec-rails
selenium-webdriver
shoulda-matchers (~> 5.0)
Expand Down
25 changes: 25 additions & 0 deletions app/components/clients/list_item_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<li class="relative flex justify-between gap-x-6 py-5">
<div class="flex min-w-0 gap-x-4">
<div class="min-w-0 flex-auto">
<p class="text-sm font-semibold leading-6 text-gray-900">
<%= link_to client_path(@client) do %>
<span class="absolute inset-x-0 -top-px bottom-0"></span>
<%= client_name %>
<% end %>
</p>
<p class="mt-1 flex text-xs leading-5 text-gray-500">
<%= client_description %>
</p>
</div>
</div>
<div class="flex shrink-0 items-center gap-x-4">
<div class="hidden sm:flex sm:flex-col sm:items-end">
<div class="mt-1 flex items-center gap-x-1.5">
<%= render(Shared::StatusComponent.new(status: client_status)) %>
</div>
</div>
<svg class="h-5 w-5 flex-none text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</div>
</li>
20 changes: 20 additions & 0 deletions app/components/clients/list_item_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Clients
class ListItemComponent < ViewComponent::Base
def initialize(client:, current_company:)
@current_company = current_company
@client = client
end

def client_name
@client.name
end

def client_description
@client.description
end

def client_status
@client.status
end
end
end
3 changes: 0 additions & 3 deletions app/components/settings/breadcrumbs_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@ module Settings
class BreadcrumbsComponent < ViewComponent::Base

renders_many :breadcrumbs, Settings::BreadcrumbComponent

def initialize()
end
end
end
2 changes: 1 addition & 1 deletion app/components/settings/users/list_item_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<p class="text-sm leading-6 text-gray-900"><%= user_job_title %></p>
<% end %>
<div class="mt-1 flex items-center gap-x-1.5">
<%= render(Settings::Users::StatusComponent.new(status: user_status)) %>
<%= render(Shared::StatusComponent.new(status: user_status)) %>
</div>
</div>
<svg class="h-5 w-5 flex-none text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
Expand Down
3 changes: 0 additions & 3 deletions app/components/settings/users/status_component.html.erb

This file was deleted.

22 changes: 0 additions & 22 deletions app/components/settings/users/status_component.rb

This file was deleted.

3 changes: 3 additions & 0 deletions app/components/shared/status_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset <%= target_status_color %>">
Status: <%= target_status.titleize %>
</span>
20 changes: 20 additions & 0 deletions app/components/shared/status_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Shared
class StatusComponent < ViewComponent::Base
def initialize(status:)
@status = status
end

def target_status
@status.capitalize
end

def target_status_color
case @status
when Membership::ACTIVE || Client::ACTIVE
"bg-green-50 text-green-700 ring-green-600/20"
else
"bg-gray-50 text-gray-600 ring-gray-500/10"
end
end
end
end
58 changes: 21 additions & 37 deletions app/controllers/clients_controller.rb
Original file line number Diff line number Diff line change
@@ -1,71 +1,55 @@
class ClientsController < ApplicationController
before_action :require_user!
before_action :set_client, only: %i[ show edit update destroy ]
before_action :set_client, only: %i[ show edit update toggle_archived ]

# GET /clients or /clients.json
def index
@clients = Client.all
@clients = current_company.clients.all
end

# GET /clients/1 or /clients/1.json
def show
end

# GET /clients/new
def new
@client = Client.new
end

# GET /clients/1/edit
def edit
end

# POST /clients or /clients.json
def create
@client = Client.new(client_params)
@client = current_company.clients.new(create_client_params)

respond_to do |format|
if @client.save
format.html { redirect_to client_url(@client), notice: "Client was successfully created." }
format.json { render :show, status: :created, location: @client }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @client.errors, status: :unprocessable_entity }
end
if @client.save
redirect_to client_url(@client), notice: "Client was successfully created."
else
render :new, status: :unprocessable_entity
end
end

# PATCH/PUT /clients/1 or /clients/1.json
def update
respond_to do |format|
if @client.update(client_params)
format.html { redirect_to client_url(@client), notice: "Client was successfully updated." }
format.json { render :show, status: :ok, location: @client }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @client.errors, status: :unprocessable_entity }
end
if @client.update(update_client_params)
redirect_to client_url(@client), notice: "Client was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end

# DELETE /clients/1 or /clients/1.json
def destroy
@client.destroy!
def toggle_archived
@client.toggle_archived!

respond_to do |format|
format.html { redirect_to clients_url, notice: "Client was successfully destroyed." }
format.json { head :no_content }
end
redirect_to clients_url, notice: "Client status was successfully updated."
end

private
# Use callbacks to share common setup or constraints between actions.
def set_client
@client = Client.find(params[:id])
@client = current_company.clients.find(params[:id])
end

def create_client_params
params.require(:client).permit(:name, :description)
end

# Only allow a list of trusted parameters through.
def client_params
params.require(:client).permit(:name, :description, :status, :company_id)
def update_client_params
params.require(:client).permit(:name, :description, :status)
end
end
51 changes: 17 additions & 34 deletions app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
@@ -1,60 +1,43 @@
class ProjectsController < ApplicationController
before_action :require_user!
before_action :set_project, only: %i[ show edit update destroy ]
before_action :set_project, only: %i[ show edit update ]

# GET /projects or /projects.json
def index
@projects = Project.includes(:company). all
end

# GET /projects/1 or /projects/1.json
def show
end

# GET /projects/new
def new
@project = Project.new
@client = Client.find_by(id: params[:client_id]) # optional
@project = Project.new(
client_id: params[:client_id],

# TODO: make these configurable?
status: Project::PROPOSED,
payment_frequency: Project::MONTHLY
)
end

# GET /projects/1/edit
def edit
end

# POST /projects or /projects.json
def create
@project = Project.new(project_params)

respond_to do |format|
if @project.save
format.html { redirect_to project_url(@project), notice: "Project was successfully created." }
format.json { render :show, status: :created, location: @project }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @project.errors, status: :unprocessable_entity }
end
if @project.save
redirect_to project_url(@project), notice: "Project was successfully created."
else
render :new, status: :unprocessable_entity
end
end

# PATCH/PUT /projects/1 or /projects/1.json
def update
respond_to do |format|
if @project.update(project_params)
format.html { redirect_to project_url(@project), notice: "Project was successfully updated." }
format.json { render :show, status: :ok, location: @project }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @project.errors, status: :unprocessable_entity }
end
end
end

# DELETE /projects/1 or /projects/1.json
def destroy
@project.destroy!

respond_to do |format|
format.html { redirect_to projects_url, notice: "Project was successfully destroyed." }
format.json { head :no_content }
if @project.update(project_params)
redirect_to project_url(@project), notice: "Project was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end

Expand Down
20 changes: 20 additions & 0 deletions app/models/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,24 @@ class Client < ApplicationRecord

scope :active, -> { where(status: 'active') }
scope :archived, -> { where(status: 'archived') }

def active?
status == ACTIVE
end

def archived?
status == ARCHIVED
end
def toggle_archived!
new_status = active? ? Client::ARCHIVED : Client::ACTIVE

Client.transaction do
update!(status: new_status)

# archive all projects
if new_status == Client::ARCHIVED
projects.update_all(status: Project::ARCHIVED)
end
end
end
end
20 changes: 20 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,24 @@ class Project < ApplicationRecord
validates :status, presence: true, inclusion: { in: VALID_STATUSES }
validates :cost, presence: true, numericality: { greater_than_or_equal_to: 0.0 }
validates :payment_frequency, presence: true, inclusion: { in: VALID_PAYMENT_FREQUENCIES }

def active?
status == ACTIVE
end

def archived?
status == ARCHIVED
end

def proposed?
status == PROPOSED
end

def cancelled?
status == CANCELLED
end

def completed?
status == COMPLETED
end
end
Loading
Loading