From 12a205e5e840aaf3ed9077d3b0238ce887ab4906 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Mon, 26 May 2025 17:16:22 -0400 Subject: [PATCH] Add neighborhood post record updating by admins --- .../admin/post_reviews_controller.rb | 30 ++++++++++++++-- app/models/neighborhood/app.rb | 5 +++ app/models/neighborhood/post.rb | 24 +++++++++++-- .../post_reviews/_time_approval_form.html.erb | 36 +++++++++++++++++++ app/views/admin/post_reviews/show.html.erb | 20 +++++++++-- config/routes.rb | 1 + 6 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 app/views/admin/post_reviews/_time_approval_form.html.erb diff --git a/app/controllers/admin/post_reviews_controller.rb b/app/controllers/admin/post_reviews_controller.rb index dbc6aaf..1777b03 100644 --- a/app/controllers/admin/post_reviews_controller.rb +++ b/app/controllers/admin/post_reviews_controller.rb @@ -2,7 +2,7 @@ class Admin::PostReviewsController < Admin::BaseController include ApplicationHelper # For short_time_simple, short_time_detailed helpers - before_action :set_post, only: [ :show ] + before_action :set_post, only: [ :show, :update ] def show # User related to the post @@ -18,7 +18,6 @@ class Admin::PostReviewsController < Admin::BaseController post_end_str = @post.airtable_fields["createdAt"] @total_post_hackatime_seconds = @post.airtable_fields["hackatimeTime"]&.to_i || 0 - if post_start_str.blank? || post_end_str.blank? raise ArgumentError, "Post start or end date is missing from Airtable. lastPost: '#{post_start_str}', createdAt: '#{post_end_str}'" end @@ -69,6 +68,10 @@ class Admin::PostReviewsController < Admin::BaseController all_heartbeats_for_user_in_review_window = all_heartbeats_for_user_in_review_window.select do |hb| selected_projects.include?(hb.project) end + else + all_heartbeats_for_user_in_review_window = all_heartbeats_for_user_in_review_window.select do |hb| + @recommended_project_names.include?(hb.project) + end end @detailed_spans = [] @@ -123,6 +126,29 @@ class Admin::PostReviewsController < Admin::BaseController @current_user_timezone = current_user.timezone end + def update + approved_seconds = params[:approved_seconds].to_i + comment = params[:comment] + + fields = { + "approved_time" => approved_seconds, + "review_comment" => comment, + "review_status" => "approved" + } + + if @post.push_to_airtable!(fields) + flash[:notice] = "Time approved successfully" + redirect_to admin_ysws_review_path(@post.app.ysws_submission.airtable_id) + else + flash[:alert] = "Failed to approve time" + redirect_to admin_post_review_path(@post.airtable_id) + end + + rescue => e + flash[:alert] = "Failed to approve time: #{e.message}" + redirect_to admin_post_review_path(@post.airtable_id) + end + private def set_post diff --git a/app/models/neighborhood/app.rb b/app/models/neighborhood/app.rb index 9e2ce62..5dc180b 100644 --- a/app/models/neighborhood/app.rb +++ b/app/models/neighborhood/app.rb @@ -16,4 +16,9 @@ class Neighborhood::App < ApplicationRecord return [] unless airtable_fields["hackatimeProjects"]&.any? Neighborhood::Project.where(airtable_id: airtable_fields["hackatimeProjects"]) end + + def ysws_submission + return nil unless airtable_fields["YSWS Project Submission"]&.first + Neighborhood::YswsSubmission.find_by(airtable_id: airtable_fields["YSWS Project Submission"].first) + end end diff --git a/app/models/neighborhood/post.rb b/app/models/neighborhood/post.rb index 718eae1..de225c4 100644 --- a/app/models/neighborhood/post.rb +++ b/app/models/neighborhood/post.rb @@ -3,12 +3,32 @@ class Neighborhood::Post < ApplicationRecord include HasTableSync - has_table_sync base: "appnsN4MzbnfMY0ai", - table: "tbl0iKxglbySiEbB4", + BASE_ID = "appnsN4MzbnfMY0ai" + TABLE_ID = "tbl0iKxglbySiEbB4" + + has_table_sync base: BASE_ID, + table: TABLE_ID, pat: ENV["NEIGHBORHOOD_AIRTABLE_PAT"] def app return nil unless airtable_fields["app"]&.first Neighborhood::App.find_by(airtable_id: airtable_fields["app"].first) end + + def push_to_airtable!(fields) + response = HTTP.patch( + "https://api.airtable.com/v0/#{BASE_ID}/#{TABLE_ID}/#{airtable_id}", + headers: { + "Authorization" => "Bearer #{ENV["NEIGHBORHOOD_AIRTABLE_PAT"]}", + "Content-Type" => "application/json" + }, + json: { fields: fields } + ) + new_fields = JSON.parse(response.body)["fields"] + if response.status.success? + update(airtable_fields: new_fields) + else + raise "Failed to push to Airtable: #{response.body}" + end + end end diff --git a/app/views/admin/post_reviews/_time_approval_form.html.erb b/app/views/admin/post_reviews/_time_approval_form.html.erb new file mode 100644 index 0000000..83f5f92 --- /dev/null +++ b/app/views/admin/post_reviews/_time_approval_form.html.erb @@ -0,0 +1,36 @@ +<%# Time Approval Form %> +
+ <%= form_with url: admin_post_review_path(post_id: @post.airtable_id), method: :patch, class: "approval-form", data: { turbo: true, turbo_method: :patch } do |f| %> +
+ <%= f.text_area :comment, placeholder: "Optional comment...", class: "w-full p-2 rounded bg-slate-700 text-slate-200", style: "width: 100%; margin-bottom: 0.5rem; background-color: #1E293B; color: #E2E8F0; border: 1px solid #475569; border-radius: 0.25rem; padding: 0.5rem;" %> +
+ +
+ <%= f.hidden_field :approved_seconds, id: "approved-seconds" %> + <%= f.submit "Approve All Time", class: "approve-all-btn", style: "background-color: #059669; color: white; padding: 0.5rem 1rem; border-radius: 0.25rem; border: none; cursor: pointer; font-size: 0.875rem;" %> + <%= f.submit "Approve Selected", class: "approve-selected-btn", style: "background-color: #2563EB; color: white; padding: 0.5rem 1rem; border-radius: 0.25rem; border: none; cursor: pointer; font-size: 0.875rem;" %> +
+ <% end %> +
+ + \ No newline at end of file diff --git a/app/views/admin/post_reviews/show.html.erb b/app/views/admin/post_reviews/show.html.erb index 620877f..3687c5e 100644 --- a/app/views/admin/post_reviews/show.html.erb +++ b/app/views/admin/post_reviews/show.html.erb @@ -155,6 +155,10 @@
<%= flash[:alert] %>
<% end %> + <% if flash[:notice] %> +
<%= flash[:notice] %>
+ <% end %> +

Post Details

<% if @user %>

User: <%= render "shared/user_mention", user: @user %>

<% end %> @@ -177,6 +181,18 @@ <% end %>

Airtable Link: <%= link_to "View on Airtable", @post.airtable_url, target: "_blank", class: "button-style" %>

API Link: <%= link_to "View on stats endpoint", api_v1_path(username: @user.id, start_date: @post_start_display.iso8601, end_date: @post_end_display.iso8601, features: "projects") %>

+

Created: <%= @post_start_display.strftime("%B %d, %Y at %I:%M %p") %>

+

Last Post: <%= @post_end_display.strftime("%B %d, %Y at %I:%M %p") %>

+

Total Time: <%= short_time_detailed(@total_post_hackatime_seconds) %>

+ <% if @post.airtable_fields["approvedTime"].present? %> +

Approved Time: <%= short_time_detailed(@post.airtable_fields["approvedTime"]) %>

+ <% end %> + <% if @post.airtable_fields["reviewComment"].present? %> +

Review Comment: <%= @post.airtable_fields["reviewComment"] %>

+ <% end %> + <% if @post.airtable_fields["reviewStatus"].present? %> +

Status: <%= @post.airtable_fields["reviewStatus"] %>

+ <% end %>
<% if @unique_project_names.any? %> @@ -185,7 +201,7 @@
<% @unique_project_names.each do |project_name| %> - <% is_selected = params[:projects].present? ? params[:projects].split(',').include?(project_name) : true %> + <% is_selected = params[:projects].present? ? params[:projects].split(',').include?(project_name) : @recommended_project_names.include?(project_name) %> <% is_recommended = @recommended_project_names.include?(project_name) %>
-
-
0s / <%= short_time_detailed(@total_post_hackatime_seconds) %>
diff --git a/config/routes.rb b/config/routes.rb index 612b5e3..366dc09 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,6 +22,7 @@ Rails.application.routes.draw do get "timeline/leaderboard_users", to: "timeline#leaderboard_users" get "post_reviews/:post_id", to: "post_reviews#show", as: :post_review + patch "post_reviews/:post_id", to: "post_reviews#update" get "post_reviews/:post_id/date/:date", to: "post_reviews#show", as: :post_review_on_date get "ysws_reviews/:record_id", to: "ysws_reviews#show", as: :ysws_review