diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..126f319 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.git +.gitignore +log/* +tmp/* +db/*.sqlite3 +db/*.sqlite3-journal +public/assets +node_modules +.bundle +Dockerfile +docker-compose.yml +README.md +test/* +spec/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a83d772 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# Use Ruby 2.6.5 as the base image +FROM ruby:2.6.5 + +# Install system dependencies +RUN apt-get update -qq && apt-get install -y \ + build-essential \ + libpq-dev \ + nodejs \ + libsqlite3-dev \ + yarn + +# Set the working directory +WORKDIR /app + +# Install bundler +RUN gem install bundler -v 2.1.4 + +# Copy Gemfile and Gemfile.lock +COPY Gemfile Gemfile.lock ./ + +# Install dependencies +RUN bundle install + +# Copy the rest of the application code +COPY . . + +# Set environment variables +ENV RAILS_ENV=production +ENV RAILS_SERVE_STATIC_FILES=true +ENV RAILS_LOG_TO_STDOUT=true + +# Precompile assets +# Note: SECRET_KEY_BASE is required for asset precompilation in some Rails versions +# You can provide a dummy value here if it's not strictly checked during precompile +RUN bundle exec rake assets:precompile SECRET_KEY_BASE=dummy_key + +# Expose port 8080 (Cloud Run default) +EXPOSE 8080 + +# Start the application +CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] diff --git a/Gemfile b/Gemfile index 869e1b2..097ae31 100644 --- a/Gemfile +++ b/Gemfile @@ -8,8 +8,8 @@ end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.1.0' -# Use sqlite3 as the database for Active Record -gem 'sqlite3' +# Use PostgreSQL as the database for Active Record +gem 'pg' # Use Puma as the app server gem 'puma', '~> 3.7' # Use SCSS for stylesheets diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f3bbd04..abc0f0c 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -15,10 +15,12 @@ */ body { - font-family: Arial, sans-serif; - max-width: 600px; - margin: 0 auto; - padding: 20px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f7f9; + color: #333; + line-height: 1.5; } h1 { @@ -70,58 +72,170 @@ h1 { margin-top: 20px; } -.task { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px; - border: 1px solid #eee; - border-radius: 4px; - margin-bottom: 8px; - background-color: #fff; +/* Task Cards */ +.task-card { + background: white; + border: 1px solid #e2e8f0; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } -.task.completed { - background-color: #f8f9fa; +.task-card:hover { + border-color: #007bff44; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + transform: translateY(-2px); } -.task.completed .task-title { +.task-card.completed { + background: #f8fafc; + opacity: 0.8; +} + +.task-card.completed .task-card-title { text-decoration: line-through; - color: #6c757d; + color: #94a3b8; } -.task-content { +.task-card-header { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 15px; +} + +@media (min-width: 768px) { + .task-card-header { + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + } +} + +.task-card-title-section { display: flex; align-items: center; + gap: 12px; flex: 1; } -.task-checkbox { - margin-right: 12px; +.task-checkbox-wrapper input[type="checkbox"] { + width: 20px; + height: 20px; + cursor: pointer; } -.task-title { +.task-card-title { + margin: 0; font-size: 16px; + font-weight: 700; + color: #1e293b; } -.task-actions { - margin-left: 12px; +.task-desc-indicator { + font-size: 14px; + opacity: 0.6; } -.delete-btn { - color: #dc3545; - text-decoration: none; - padding: 4px 8px; - border: 1px solid #dc3545; - border-radius: 3px; - font-size: 12px; +.task-card-badges { + display: flex; + gap: 8px; + flex-wrap: wrap; } -.delete-btn:hover { - background-color: #dc3545; +.badge-priority { + font-size: 11px; + font-weight: 700; + padding: 4px 10px; + border-radius: 6px; + border: 1px solid transparent; + text-transform: uppercase; +} + +.badge-status { color: white; + font-size: 10px; + font-weight: 800; + padding: 4px 10px; + border-radius: 20px; + text-transform: uppercase; + letter-spacing: 0.5px; } +.task-card-body { + padding: 12px 0; + border-top: 1px solid #f1f5f9; +} + +.task-card-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 12px; +} + +.info-item { + display: flex; + flex-direction: column; + gap: 2px; +} + +.info-label { + font-size: 10px; + font-weight: 800; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.info-value { + font-size: 13px; + font-weight: 600; + color: #475569; +} + +.task-card-footer { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #f1f5f9; + display: flex; + justify-content: flex-end; +} + +.task-card-actions { + display: flex; + gap: 8px; +} + +.btn-task-action { + font-size: 12px; + font-weight: 700; + padding: 6px 14px; + border-radius: 6px; + text-decoration: none; + background: #f1f5f9; + color: #475569; + transition: all 0.2s; +} + +.btn-task-action:hover { + background: #e2e8f0; + color: #1e293b; +} + +.btn-task-action.action-delete { + color: #ef4444; + background: #fef2f2; +} + +.btn-task-action.action-delete:hover { + background: #fee2e2; + color: #dc2626; +} + +/* Global States */ + .empty-state { text-align: center; color: #6c757d; @@ -217,29 +331,49 @@ h1 { .dashboard-header { background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); color: white; - padding: 30px; + padding: 8px 15px; border-radius: 8px; - margin-bottom: 30px; + margin-bottom: 12px; display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + gap: 4px; + text-align: center; +} + +@media (min-width: 768px) { + .dashboard-header { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 8px 20px; + text-align: left; + } } .dashboard-header h1 { margin: 0; - font-size: 28px; + font-size: 18px; + font-weight: 800; + letter-spacing: -0.5px; +} + +@media (min-width: 768px) { + .dashboard-header h1 { + font-size: 22px; + } } .user-welcome { text-align: right; + font-size: 13px; } .user-role { - background: rgba(255, 255, 255, 0.2); - padding: 4px 8px; - border-radius: 12px; - font-size: 12px; - margin-left: 10px; + background: rgba(255, 255, 255, 0.15); + padding: 3px 10px; + border-radius: 20px; + font-size: 11px; + font-weight: 600; } .user-department { @@ -255,24 +389,30 @@ h1 { .stats-section { background: white; - padding: 25px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 24px; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } .stats-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 15px; margin-bottom: 30px; } .stat-card { - background: #f8f9fa; + background: #f8fafc; padding: 20px; - border-radius: 6px; + border-radius: 10px; text-align: center; - border-left: 4px solid #007bff; + border: 1px solid #e2e8f0; + transition: transform 0.2s; +} + +.stat-card:hover { + transform: translateY(-2px); + border-color: #007bff44; } .stat-number { @@ -311,10 +451,18 @@ h1 { .dept-card, .member-card { - background: #f8f9fa; - padding: 15px; - border-radius: 6px; - border: 1px solid #e9ecef; + background: #f8fafc; + padding: 16px; + border-radius: 10px; + border: 1px solid #e2e8f0; + transition: all 0.2s; +} + +.dept-card:hover, +.member-card:hover { + background: white; + border-color: #007bff44; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .dept-card h4, @@ -338,17 +486,64 @@ h1 { .recent-tasks-section { background: white; - padding: 25px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 24px; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } -.recent-tasks-section h2 { +.recent-tasks-section h2, +.stats-section h2, +.new-task-section h2, +.tasks-section h2 { margin-top: 0; margin-bottom: 20px; - color: #333; - border-bottom: 2px solid #eee; - padding-bottom: 10px; + color: #1e293b; + font-size: 1.25rem; + font-weight: 700; + border-bottom: 2px solid #f1f5f9; + padding-bottom: 12px; +} + +/* Global Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: all 0.2s; + border: 1px solid transparent; +} + +.btn-primary { + background: #007bff; + color: white; +} + +.btn-primary:hover { + background: #0056b3; +} + +.btn-success { + background: #22c55e; + color: white; +} + +.btn-success:hover { + background: #16a34a; +} + +.btn-secondary { + background: #64748b; + color: white; +} + +.btn-secondary:hover { + background: #475569; } .dashboard-actions { @@ -358,163 +553,100 @@ h1 { justify-content: center; } -.action-btn { - padding: 12px 24px; - border-radius: 6px; - text-decoration: none; - font-weight: 600; - transition: all 0.2s; -} - -.action-btn.primary { - background-color: #007bff; - color: white; -} - -.action-btn.primary:hover { - background-color: #0056b3; -} - -.action-btn.success { - background-color: #28a745; - color: white; -} - -.action-btn.success:hover { - background-color: #1e7e34; -} - -.action-btn.secondary { - background-color: #6c757d; - color: white; -} - -.action-btn.secondary:hover { - background-color: #545b62; -} - /* Layout Updates */ .content-layout { - display: grid; - grid-template-columns: 250px 1fr; - gap: 30px; - margin-top: 20px; + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; + max-width: 1400px; + margin: 0 auto; +} + +@media (min-width: 1024px) { + .content-layout { + flex-direction: row; + align-items: flex-start; + } } .sidebar { background: white; padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); height: fit-content; + width: 100%; } -.filter-section h3, -.actions-section h3 { - margin-top: 0; - margin-bottom: 20px; - color: #333; - font-size: 16px; +@media (min-width: 1024px) { + .sidebar { + width: 300px; + position: sticky; + top: 20px; + } +} + +.combined-filter-form { + display: flex; + flex-direction: column; + gap: 15px; } .filter-group { - margin-bottom: 20px; + margin-bottom: 0; } .filter-group label { display: block; - margin-bottom: 8px; - font-weight: 600; - color: #333; + margin-bottom: 5px; + font-size: 13px; + font-weight: 700; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.5px; } .filter-select { width: 100%; - padding: 8px 12px; - border: 1px solid #ddd; - border-radius: 4px; + padding: 10px 12px; + border: 1px solid #e2e8f0; + border-radius: 8px; font-size: 14px; + color: #1e293b; + background-color: #f8fafc; + transition: all 0.2s; +} + +.filter-select:focus { + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); + outline: none; } .main-content { background: white; - padding: 25px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + flex: 1; + width: 100%; + box-sizing: border-box; +} + +@media (min-width: 768px) { + .main-content { + padding: 30px; + } } .new-task-section { margin-bottom: 30px; } -.new-task-section h2, -.tasks-section h2 { - margin-top: 0; - margin-bottom: 20px; - color: #333; -} +/* Heading section replaced above */ -/* Enhanced Task Styles */ -.task { - border-left: 4px solid #ddd; - transition: all 0.3s; -} - -.task-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; - padding-bottom: 8px; - border-bottom: 1px solid #eee; -} - -.task-priority { - font-weight: 600; - font-size: 14px; -} - -.task-status-badge { - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 12px; - font-weight: 600; -} - -.task-meta { - display: flex; - gap: 10px; - flex-wrap: wrap; - margin-top: 10px; - padding-top: 10px; - border-top: 1px solid #eee; -} - -.department-tag, -.assignee-tag, -.created-tag { - background: #f8f9fa; - padding: 2px 6px; - border-radius: 3px; - font-size: 11px; - color: #6c757d; - border: 1px solid #dee2e6; -} - -.department-tag { - border-color: #007bff; - color: #007bff; -} - -.assignee-tag { - border-color: #28a745; - color: #28a745; -} - -.created-tag { - border-color: #6c757d; -} +/* Forms & Inputs */ /* Enhanced Form Styles */ .form-row { @@ -531,7 +663,7 @@ h1 { .field-group { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } - + .stats-grid { grid-template-columns: repeat(4, 1fr); } @@ -541,7 +673,7 @@ h1 { .dashboard-content { grid-template-columns: 2fr 1fr; } - + .content-layout { grid-template-columns: 300px 1fr; } @@ -644,7 +776,8 @@ h1 { opacity: 0.7; } -.edit-btn, .details-btn { +.edit-btn, +.details-btn { color: #007bff; text-decoration: none; padding: 4px 8px; @@ -654,7 +787,8 @@ h1 { margin-right: 5px; } -.edit-btn:hover, .details-btn:hover { +.edit-btn:hover, +.details-btn:hover { background-color: #007bff; color: white; } @@ -795,7 +929,7 @@ h1 { .field-group { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } - + .stats-grid { grid-template-columns: repeat(4, 1fr); } @@ -805,7 +939,7 @@ h1 { .dashboard-content { grid-template-columns: 2fr 1fr; } - + .content-layout { grid-template-columns: 300px 1fr; } @@ -813,8 +947,8 @@ h1 { /* Navigation Styles */ .main-nav { - background: linear-gradient(135deg, #343a40 0%, #2c3e50 100%); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); padding: 0; position: sticky; top: 0; @@ -822,73 +956,103 @@ h1 { } .nav-container { - max-width: 1200px; + max-width: 1400px; margin: 0 auto; display: flex; - justify-content: space-between; - align-items: center; - padding: 15px 20px; + flex-direction: column; + padding: 10px 20px; + gap: 15px; +} + +@media (min-width: 768px) { + .nav-container { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + } } .nav-brand { display: flex; align-items: center; + justify-content: center; } .brand-link { - color: white; - font-size: 24px; - font-weight: bold; + color: #007bff; + font-size: 20px; + font-weight: 800; text-decoration: none; - transition: color 0.3s; -} - -.brand-link:hover { - color: #f8f9fa; + letter-spacing: -0.5px; } .nav-menu { display: flex; - gap: 30px; - align-items: center; + gap: 10px; + justify-content: center; + flex-wrap: wrap; +} + +@media (min-width: 768px) { + .nav-menu { + gap: 20px; + } } .nav-link { - color: rgba(255, 255, 255, 0.8); + color: #64748b; text-decoration: none; - font-weight: 500; - padding: 8px 16px; - border-radius: 6px; - transition: all 0.3s; + font-weight: 600; + font-size: 14px; + padding: 8px 12px; + border-radius: 8px; + transition: all 0.2s; } .nav-link:hover { - background: rgba(255, 255, 255, 0.1); - color: white; + background: #f1f5f9; + color: #007bff; } .nav-user { display: flex; align-items: center; + justify-content: center; gap: 15px; + padding-top: 10px; + border-top: 1px solid #f1f5f9; +} + +@media (min-width: 768px) { + .nav-user { + padding-top: 0; + border-top: none; + } } .user-info { - color: white; + color: #1e293b; + font-size: 13px; font-weight: 600; + background: #f1f5f9; + padding: 6px 12px; + border-radius: 20px; } .nav-link.sign-out { - background: #dc3545; - border-color: #dc3545; + background: #fee2e2; + color: #ef4444; } .nav-link.sign-out:hover { - background: #c82333; + background: #fecaca; + color: #dc2626; } /* Flash Messages */ -.notice, .alert { +.notice, +.alert { padding: 15px 20px; margin: 20px 0; border-radius: 6px; @@ -978,62 +1142,117 @@ h1 { } /* Department Views Styles */ +.content-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 20px 40px 20px; +} + .departments-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; margin-top: 20px; } -.department-card { +.department-card-modern { background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - border: 1px solid #e9ecef; - transition: transform 0.2s, box-shadow 0.2s; + border-radius: 12px; + padding: 24px; + border: 1px solid #e2e8f0; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; } -.department-card:hover { +.department-card-modern:hover { transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + border-color: #007bff44; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); } -.department-card h3 { - margin: 0 0 15px 0; - color: #007bff; +.department-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 12px; } -.department-card h3 a { - color: #007bff; +.department-card-header h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 700; +} + +.department-card-header h3 a { + color: #1e293b; text-decoration: none; + transition: color 0.2s; } -.department-card h3 a:hover { - text-decoration: underline; +.department-card-header h3 a:hover { + color: #007bff; } -.department-stats { +.department-desc { + font-size: 14px; + color: #64748b; + margin-bottom: 24px; + line-height: 1.6; + flex-grow: 1; +} + +.department-stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 10px; - margin-top: 15px; - padding-top: 15px; - border-top: 1px solid #eee; + gap: 12px; + padding-top: 20px; + border-top: 1px solid #f1f5f9; } -.stat { - text-align: center; - font-size: 14px; +.dept-stat-box { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; } -.stat strong { - display: block; - color: #333; +.dept-stat-label { + font-size: 10px; + font-weight: 800; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.dept-stat-value { font-size: 18px; - margin-bottom: 5px; + font-weight: 700; + color: #1e293b; } +.text-primary { + color: #007bff; +} + +.btn-mini { + font-size: 11px; + font-weight: 700; + color: #64748b; + text-decoration: none; + padding: 4px 8px; + background: #f1f5f9; + border-radius: 4px; +} + +.btn-mini:hover { + background: #e2e8f0; + color: #1e293b; +} + +/* Old dept styles removed */ + .department-info { background: white; padding: 25px; @@ -1097,5 +1316,85 @@ h1 { justify-content: center; margin-top: 20px; } - + + +/* Comments Section Styles */ +.comments-section { + margin-top: 40px; + padding-top: 30px; + border-top: 2px solid #eee; +} + +.comments-section h2 { + color: #333; + font-size: 20px; + margin-bottom: 20px; +} + +.comment-form { + margin-bottom: 30px; + background: #f8f9fa; + padding: 20px; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.comment-textarea { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + font-family: inherit; + resize: vertical; + min-height: 80px; + margin-bottom: 15px; + box-sizing: border-box; +} + +.comment-textarea:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.comment { + background: white; + padding: 15px; + border-radius: 8px; + border: 1px solid #eee; + margin-bottom: 15px; + transition: all 0.2s ease; +} + +.comment:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + border-color: #d1d9e6; +} + +.comment-header { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + font-size: 13px; +} + +.comment-user { + font-weight: 700; + color: #007bff; +} + +.comment-date { + color: #999; +} + +.comment-content { + color: #444; + line-height: 1.5; + font-size: 14px; +} + +.comment-content p { + margin: 0; +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7fbd626..8523b69 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..16ad41d --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -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 diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 8934a24..3257215 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -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 diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..3dfe9ac --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,6 @@ +class Comment < ApplicationRecord + belongs_to :user + belongs_to :task + + validates :content, presence: true +end diff --git a/app/models/task.rb b/app/models/task.rb index 9723042..38a0ea2 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -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', diff --git a/app/models/user.rb b/app/models/user.rb index 3af1d57..f56b7b2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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' } diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb new file mode 100644 index 0000000..b60a8c3 --- /dev/null +++ b/app/views/comments/_comment.html.erb @@ -0,0 +1,9 @@ +
No departments found.
- <% else %> - <% @departments.each do |department| %> -<%= department.description %>
- -No departments found.
+<%= department.description&.truncate(100) || "No description provided." %>
+ +