Move github source to private server
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
include AuthorizationConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
before_action :set_current_user_department
|
||||
|
||||
|
||||
31
app/controllers/comments_controller.rb
Normal file
31
app/controllers/comments_controller.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
class CommentsController < ApplicationController
|
||||
before_action :set_task
|
||||
|
||||
def create
|
||||
authorize_task!
|
||||
@comment = @task.comments.build(comment_params)
|
||||
@comment.user = current_user
|
||||
|
||||
if @comment.save
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, notice: 'Comment added.' }
|
||||
format.js
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, alert: 'Comment cannot be blank.' }
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_task
|
||||
@task = Task.find(params[:task_id])
|
||||
end
|
||||
|
||||
def comment_params
|
||||
params.require(:comment).permit(:content)
|
||||
end
|
||||
end
|
||||
@@ -53,7 +53,6 @@ class TasksController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize_task_update!(@task)
|
||||
@departments = accessible_departments.ordered
|
||||
end
|
||||
|
||||
@@ -72,7 +71,14 @@ class TasksController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
if @task.update(task_params)
|
||||
updated_params = task_params.to_h
|
||||
if updated_params[:status] == "1"
|
||||
updated_params[:status] = "completed"
|
||||
elsif updated_params[:status] == "0"
|
||||
updated_params[:status] = "open"
|
||||
end
|
||||
|
||||
if @task.update(updated_params)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, notice: 'Task was successfully updated.' }
|
||||
format.js
|
||||
|
||||
6
app/models/comment.rb
Normal file
6
app/models/comment.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Comment < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :task
|
||||
|
||||
validates :content, presence: true
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class Task < ApplicationRecord
|
||||
belongs_to :creator, class_name: 'User'
|
||||
|
||||
has_many :task_activities, dependent: :destroy
|
||||
has_many :comments, dependent: :destroy
|
||||
|
||||
enum priority: {
|
||||
planning: 'planning',
|
||||
|
||||
@@ -7,6 +7,7 @@ class User < ApplicationRecord
|
||||
belongs_to :department, optional: true
|
||||
has_many :assigned_tasks, class_name: 'Task', foreign_key: 'assignee_id'
|
||||
has_many :created_tasks, class_name: 'Task', foreign_key: 'creator_id'
|
||||
has_many :comments, dependent: :destroy
|
||||
|
||||
enum role: { admin: 'admin', manager: 'manager', employee: 'employee' }
|
||||
|
||||
|
||||
9
app/views/comments/_comment.html.erb
Normal file
9
app/views/comments/_comment.html.erb
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="comment" id="comment_<%= comment.id %>">
|
||||
<div class="comment-header">
|
||||
<span class="comment-user"><%= comment.user.name %></span>
|
||||
<span class="comment-date"><%= time_ago_in_words(comment.created_at) %> ago</span>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<%= simple_format(comment.content) %>
|
||||
</div>
|
||||
</div>
|
||||
8
app/views/comments/_form.html.erb
Normal file
8
app/views/comments/_form.html.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
<%= form_with(model: [task, comment], local: false, class: "comment-form") do |f| %>
|
||||
<div class="field">
|
||||
<%= f.text_area :content, placeholder: "Add a comment...", class: "comment-textarea", required: true %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<%= f.submit "Post Comment", class: "submit-btn" %>
|
||||
</div>
|
||||
<% end %>
|
||||
16
app/views/comments/create.js.erb
Normal file
16
app/views/comments/create.js.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
var commentsList = document.getElementById('comments-list');
|
||||
var commentForm = document.querySelector('.comment-form');
|
||||
|
||||
if (commentsList && <%= @comment.persisted? %>) {
|
||||
var newComment = '<%= j render partial: "comments/comment", locals: { comment: @comment } %>';
|
||||
commentsList.insertAdjacentHTML('afterbegin', newComment);
|
||||
commentForm.reset();
|
||||
|
||||
// Optional: add a small animation
|
||||
var commentElement = document.getElementById('comment_<%= @comment.id %>');
|
||||
commentElement.style.opacity = '0';
|
||||
commentElement.style.transition = 'opacity 0.5s';
|
||||
setTimeout(function() {
|
||||
commentElement.style.opacity = '1';
|
||||
}, 10);
|
||||
}
|
||||
@@ -1,36 +1,50 @@
|
||||
<h1>Departments</h1>
|
||||
<div class="dashboard-header">
|
||||
<h1>Departments</h1>
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'New Department', new_department_path, class: 'btn btn-success' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if flash[:notice] %>
|
||||
<div class="notice"><%= flash[:notice] %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="departments-grid">
|
||||
<% if @departments.empty? %>
|
||||
<p class="empty-state">No departments found.</p>
|
||||
<% else %>
|
||||
<% @departments.each do |department| %>
|
||||
<div class="department-card">
|
||||
<h3><%= link_to department.name, department_path(department) %></h3>
|
||||
<p><%= department.description %></p>
|
||||
|
||||
<div class="department-stats">
|
||||
<div class="stat">
|
||||
<strong>Users:</strong> <%= department.user_count %>
|
||||
<div class="content-container">
|
||||
<div class="departments-grid">
|
||||
<% if @departments.empty? %>
|
||||
<div class="empty-state-card">
|
||||
<p class="empty-state">No departments found.</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<% @departments.each do |department| %>
|
||||
<div class="department-card-modern">
|
||||
<div class="department-card-header">
|
||||
<h3><%= link_to department.name, department_path(department) %></h3>
|
||||
<div class="department-actions-mini">
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'Edit', edit_department_path(department), class: 'btn-mini' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<strong>Tasks:</strong> <%= department.task_count %>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<strong>Open:</strong> <%= department.incomplete_task_count %>
|
||||
|
||||
<p class="department-desc"><%= department.description&.truncate(100) || "No description provided." %></p>
|
||||
|
||||
<div class="department-stats-grid">
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Users</span>
|
||||
<span class="dept-stat-value"><%= department.user_count %></span>
|
||||
</div>
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Tasks</span>
|
||||
<span class="dept-stat-value"><%= department.task_count %></span>
|
||||
</div>
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Open</span>
|
||||
<span class="dept-stat-value text-primary"><%= department.incomplete_task_count %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="actions-section">
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'New Department', new_department_path, class: 'action-btn primary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Project Management System</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<%= csrf_meta_tags %>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
|
||||
@@ -1,58 +1,53 @@
|
||||
<div id="task_<%= task.id %>" class="task <%= 'completed' if task.status == 'completed' %>">
|
||||
<div class="task-header">
|
||||
<div class="task-priority" style="color: <%= task.priority_color %>">
|
||||
<%= task.priority_icon %> <%= task.priority.humanize %>
|
||||
</div>
|
||||
<div class="task-status-badge" style="background-color: <%= task.status_badge[:color] %>">
|
||||
<%= task.status_badge[:text] %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= form_with(model: task, local: false, method: :patch, class: "toggle-form") do |form| %>
|
||||
<div class="task-content">
|
||||
<%= form.check_box :status, { checked: task.status == 'completed', onchange: "this.form.submit();" }, { class: "task-checkbox" } %>
|
||||
|
||||
<div class="task-info">
|
||||
<span class="task-title"
|
||||
title="<%= task.description&.truncate(100) || 'No description' %>"
|
||||
data-description="<%= task.description || '' %>">
|
||||
<%= task.title %>
|
||||
</span>
|
||||
<% if task.description.present? %>
|
||||
<span class="description-indicator">📝</span>
|
||||
<div id="task_<%= task.id %>" class="task-card <%= task.status %>" data-priority="<%= task.priority %>">
|
||||
<div class="task-card-header">
|
||||
<div class="task-card-title-section">
|
||||
<div class="task-checkbox-wrapper">
|
||||
<%= form_with(model: task, local: false, method: :patch) do |f| %>
|
||||
<%= f.check_box :status, { checked: task.status == 'completed', onchange: "this.form.submit();" }, "completed", "open" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<h3 class="task-card-title"><%= task.title %></h3>
|
||||
<% if task.description.present? %>
|
||||
<span class="task-desc-indicator" title="Has description">📝</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="task-meta">
|
||||
<% if task.department %>
|
||||
<span class="department-tag">
|
||||
<%= task.department.name %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if task.assignee %>
|
||||
<span class="assignee-tag">
|
||||
Assigned to: <%= task.assignee.name %>
|
||||
<div class="task-card-badges">
|
||||
<span class="badge-priority" style="color: <%= task.priority_color %>; border-color: <%= task.priority_color %>22; background-color: <%= task.priority_color %>11;">
|
||||
<%= task.priority_icon %> <%= task.priority.humanize %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<span class="created-tag">
|
||||
Created: <%= task.created_at.strftime('%m/%d/%Y') %>
|
||||
</span>
|
||||
<span class="badge-status" style="background-color: <%= task.status_badge[:color] %>">
|
||||
<%= task.status_badge[:text] %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-actions">
|
||||
<%= link_to 'View', task, class: 'details-btn' %>
|
||||
<% if task.updateable_by?(current_user) %>
|
||||
<%= link_to 'Edit', edit_task_path(task), class: 'edit-btn' %>
|
||||
<% end %>
|
||||
<% if task.assign?(current_user) && !task.assignee %>
|
||||
<%= link_to 'Assign', assign_task_path(task), method: :patch, class: 'assign-btn' %>
|
||||
<% end %>
|
||||
<% if task.updateable_by?(current_user) || current_user.admin? %>
|
||||
<%= link_to 'Delete', task, method: :delete, data: { confirm: 'Are you sure?' }, class: 'delete-btn' %>
|
||||
<% end %>
|
||||
|
||||
<div class="task-card-body">
|
||||
<div class="task-card-info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Dept</span>
|
||||
<span class="info-value"><%= task.department&.name || 'Personal' %></span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Assignee</span>
|
||||
<span class="info-value"><%= task.assignee&.name || 'Unassigned' %></span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Created</span>
|
||||
<span class="info-value"><%= task.created_at.strftime('%b %d, %Y') %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-card-footer">
|
||||
<div class="task-card-actions">
|
||||
<%= link_to 'View', task, class: 'btn-task-action' %>
|
||||
<% if task.updateable_by?(current_user) %>
|
||||
<%= link_to 'Edit', edit_task_path(task), class: 'btn-task-action' %>
|
||||
<% end %>
|
||||
<% if task.updateable_by?(current_user) || current_user.admin? %>
|
||||
<%= link_to 'Delete', task, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn-task-action action-delete' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -119,10 +119,10 @@
|
||||
</div>
|
||||
|
||||
<div class="dashboard-actions">
|
||||
<%= link_to 'View All Tasks', tasks_path, class: 'action-btn primary' %>
|
||||
<%= link_to 'Create New Task', new_task_path, class: 'action-btn success' %>
|
||||
<%= link_to 'View All Tasks', tasks_path, class: 'btn btn-primary' %>
|
||||
<%= link_to 'Create New Task', new_task_path, class: 'btn btn-success' %>
|
||||
<% if current_user.admin? || current_user.manager? %>
|
||||
<%= link_to 'Manage Departments', departments_path, class: 'action-btn secondary' %>
|
||||
<%= link_to 'Manage Departments', departments_path, class: 'btn btn-secondary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,52 +21,38 @@
|
||||
<div class="sidebar">
|
||||
<div class="filter-section">
|
||||
<h3>Filters</h3>
|
||||
|
||||
<% if accessible_departments.count > 1 %>
|
||||
<div class="filter-group">
|
||||
<label>Department</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "combined-filter-form") do |f| %>
|
||||
<% if accessible_departments.count > 1 %>
|
||||
<div class="filter-group">
|
||||
<label>Department</label>
|
||||
<%= f.select :department_id,
|
||||
options_for_select([[ "All Departments", "" ]] + accessible_departments.map { |d| [d.name, d.id] },
|
||||
params[:department_id]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Priority</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Priority</label>
|
||||
<%= f.select :priority,
|
||||
options_for_select([[ "All Priorities", "" ]] + Task.priorities.keys.map { |p| [p.humanize, p] },
|
||||
params[:priority]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Status</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Status</label>
|
||||
<%= f.select :status,
|
||||
options_for_select([[ "All Statuses", "" ]] + Task.statuses.keys.map { |s| [s.humanize, s] },
|
||||
params[:status]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-section">
|
||||
<h3>Actions</h3>
|
||||
<%= link_to 'Dashboard', dashboard_path, class: 'action-btn' %>
|
||||
<%= link_to 'My Tasks', my_tasks_path, class: 'action-btn' if current_user.employee? %>
|
||||
<%= link_to 'Departments', departments_path, class: 'action-btn' if current_user.admin? || current_user.manager? %>
|
||||
<% if current_user.admin? %>
|
||||
<%= link_to 'New User', new_user_registration_path, class: 'action-btn' %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="comments-section">
|
||||
<h2>Conversations</h2>
|
||||
<%= render 'comments/form', task: @task, comment: Comment.new %>
|
||||
|
||||
<div id="comments-list">
|
||||
<%= render partial: 'comments/comment', collection: @task.comments.order(created_at: :desc) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-actions">
|
||||
<%= form_with(model: @task, local: false, method: :patch, class: "toggle-status-form") do |form| %>
|
||||
<%= form.hidden_field :completed, value: !@task.completed %>
|
||||
|
||||
Reference in New Issue
Block a user