Skip to content
Open
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ AllCops:
- storage/**/*
- tmp/**/*
- bin/**/*

Metrics/PerceivedComplexity:
Exclude:
- app/controllers/api/reports_controller.rb
1 change: 1 addition & 0 deletions app/controllers/api/reports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def index
@reports = @reports.where(user_id: params[:user_id]) if params[:user_id].present?
@reports = @reports.limit(params[:limit].to_i) if params[:limit].present?
@reports = @reports.joins(:user).where(users: { company_id: params[:company_id] }) if params[:company_id]
@reports = @reports.with_grant_practices if params[:with_grant] == 'true'
return unless params[:target] == 'unchecked_reports'
return head :forbidden unless current_user.admin_or_mentor?

Expand Down
32 changes: 30 additions & 2 deletions app/javascript/components/Reports.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import useSWR from 'swr'
import fetcher from '../fetcher'
import LoadingListPlaceholder from './LoadingListPlaceholder'
Expand All @@ -15,21 +15,47 @@ export default function Reports({
displayUserIcon = true,
companyId = '',
practiceId = '',
displayPagination = true
displayPagination = true,
practiceSourceId = null
}) {
const per = 20
const isGrantCourse = practiceSourceId !== null
const { page, setPage } = usePage()
const [userPracticeId, setUserPracticeId] = useState('')
const [withGrant, setWithGrant] = useState(false)

let reportsUrl = `/api/reports.json?page=${page}`
if (userId) reportsUrl += `&user_id=${userId}`
if (companyId) reportsUrl += `&company_id=${companyId}`
const pid = userPracticeId || practiceId
if (pid) reportsUrl += `&practice_id=${pid}`
if (unchecked) reportsUrl += `&target=unchecked_reports`
if (isGrantCourse && withGrant) reportsUrl += `&with_grant=true`

const { data, error } = useSWR(reportsUrl, fetcher)

useEffect(() => {
if (!isGrantCourse) return

let grantFilter = null
const initGrantFilter = async () => {
const targetElement = document.querySelector('[data-grant-filter]')
if (!targetElement) return

const GrantFilter = (await import('../grant-filter')).default
grantFilter = new GrantFilter(withGrant, setWithGrant)
grantFilter.render(targetElement)
}
initGrantFilter()

return () => {
if (grantFilter) {
grantFilter.destroy()
grantFilter = null
}
}
}, [data, withGrant])

if (error) return <>エラーが発生しました。</>
if (!data) {
return (
Expand All @@ -47,6 +73,7 @@ export default function Reports({
<div className="page-main is-react">
{data.totalPages === 0 && (
<div>
{isGrantCourse && <div data-grant-filter></div>}
{practices && (
<PracticeFilterDropdown
practices={practices}
Expand All @@ -59,6 +86,7 @@ export default function Reports({
)}
{data.totalPages > 0 && (
<div>
{isGrantCourse && <div data-grant-filter></div>}
{practices && (
<PracticeFilterDropdown
practices={practices}
Expand Down
57 changes: 57 additions & 0 deletions app/javascript/grant-filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export default class {
constructor(withGrant, setWithGrant) {
this.withGrant = withGrant
this.setWithGrant = setWithGrant
this.tabs = null
this.handleSelect = null
}

render(element) {
element.innerHTML = `
<nav class="pill-nav">
<ul class="pill-nav__items">
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="false">全て</button>
</li>
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="true">給付金コース</button>
</li>
</ul>
</nav
`
Comment on lines +9 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

HTML構文エラー: </nav> の閉じタグが不完全です。

Line 20 で </nav となっており、> が欠落しています。これによりHTMLのパースが正しく行われない可能性があります。

🐛 修正案
     element.innerHTML = `
     <nav class="pill-nav">
       <ul class="pill-nav__items">
         <li class="pill-nav__item">
           <button class="pill-nav__item-link" data-with-grant="false">全て</button>
         </li>
         <li class="pill-nav__item">
           <button class="pill-nav__item-link" data-with-grant="true">給付金コース</button>
         </li>
       </ul>
-    </nav
+    </nav>
     `
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
render(element) {
element.innerHTML = `
<nav class="pill-nav">
<ul class="pill-nav__items">
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="false">全て</button>
</li>
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="true">給付金コース</button>
</li>
</ul>
</nav
`
render(element) {
element.innerHTML = `
<nav class="pill-nav">
<ul class="pill-nav__items">
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="false">全て</button>
</li>
<li class="pill-nav__item">
<button class="pill-nav__item-link" data-with-grant="true">給付金コース</button>
</li>
</ul>
</nav>
`
🤖 Prompt for AI Agents
In `@app/javascript/grant-filter.js` around lines 9 - 21, The template string in
render(element) contains a malformed closing tag `</nav` (missing the trailing
'>') which breaks HTML parsing; update the template literal in the
render(element) method (in grant-filter.js) to use a proper closing `</nav>` tag
so the generated markup is valid.


this.tabs = element.querySelectorAll('.pill-nav__item-link')

this.tabs.forEach((tab) => {
const tabWithGrant = tab.dataset.withGrant === 'true'
if (tabWithGrant === this.withGrant) {
tab.classList.add('is-active')
}
})

this.handleSelect = (event) => {
const selectedTab = event.currentTarget
this.tabs.forEach((tab) => {
tab.classList.remove('is-active')
})
selectedTab.classList.add('is-active')
const tabWithGrant = selectedTab.dataset.withGrant === 'true'
this.setWithGrant(tabWithGrant)
}

this.tabs.forEach((tab) => {
tab.addEventListener('click', this.handleSelect)
})
}

destroy() {
if (!this.tabs || !this.handleSelect) return

this.tabs.forEach((tab) => {
tab.removeEventListener('click', this.handleSelect)
})

this.tabs = null
this.handleSelect = null
}
}
4 changes: 4 additions & 0 deletions app/models/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ class Report < ApplicationRecord # rubocop:todo Metrics/ClassLength

scope :user, ->(user) { where(user_id: user.id) }

scope :with_grant_practices, lambda {
joins(:practices).where.not(id: Report.joins(:practices).where(practices: { source_id: nil })).distinct
}

def self.ransackable_attributes(_auth_object = nil)
%w[title description reported_on emotion wip created_at updated_at user_id]
end
Expand Down
2 changes: 1 addition & 1 deletion app/views/practices/reports/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

.page-body
.container.is-md
= react_component('Reports', practiceId: @practice.id)
= react_component('Reports', practiceId: @practice.id, practiceSourceId: @practice.source_id)
90 changes: 0 additions & 90 deletions db/fixtures/courses_categories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,93 +132,3 @@ courses_category27:
course: course1
category: category23
position: 19

courses_category28:
course: course5
category: category1
position: 1

courses_category29:
course: course5
category: category2
position: 2

courses_category30:
course: course5
category: category3
position: 3

courses_category31:
course: course5
category: category4
position: 4

courses_category32:
course: course5
category: category5
position: 5

courses_category33:
course: course5
category: category6
position: 6

courses_category34:
course: course5
category: category7
position: 7

courses_category35:
course: course5
category: category8
position: 8

courses_category36:
course: course5
category: category9
position: 9

courses_category37:
course: course5
category: category10
position: 10

courses_category38:
course: course5
category: category11
position: 11

courses_category39:
course: course5
category: category12
position: 12

courses_category40:
course: course5
category: category13
position: 13

courses_category41:
course: course5
category: category14
position: 14

courses_category42:
course: course5
category: category15
position: 15

courses_category43:
course: course5
category: category16
position: 16

courses_category44:
course: course5
category: category22
position: 17

courses_category45:
course: course5
category: category23
position: 18
5 changes: 5 additions & 0 deletions db/fixtures/discord_profiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,8 @@ discord_profile_new-mentor:
user: new-mentor
account_name: new-mentor
times_url:

discord_profile_grant-course:
user: grant-course
account_name: grant-course
times_url:
8 changes: 8 additions & 0 deletions db/fixtures/reports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,11 @@ report90:
description: |-
たくさんのスタンプがつきました。
reported_on: "2025-10-01 01:00:00"

report91:
user: grant-course
title: 給付金コースの日報
emotion: 2
description: |-
給付金コースの日報です。
reported_on: "2025-12-01 01:00:00"
4 changes: 4 additions & 0 deletions db/fixtures/talks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,7 @@ talk_pjord:
talk_new-mentor:
user: new-mentor
action_completed: true

talk_grant_course:
user: grant-course
action_completed: true
20 changes: 20 additions & 0 deletions db/fixtures/users.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1555,3 +1555,23 @@ new-mentor:
created_at: <%= Time.current %>
sent_student_followup_message: true
last_activity_at: <%= Time.current %>

grant-course:
login_name: grant-course
email: [email protected]
crypted_password: $2a$10$n/xv4/1luueN6plzm2OyDezWlZFyGHjQEf4hwAW1r3k.lCm0frPK. # testtest
salt: zW3kQ9ubsxQQtzzzs4ap
name: 給付金コースのユーザー
name_kana: 給付金コースのユーザー
github_account: grant-course
twitter_account: grant-course
facebook_url: https://www.facebook.com/fjordllc/grant-course
blog_url: http://grant-course.org
description: "給付金コースを受講中のユーザーです。"
course: course5
job: office_worker
os: mac
organization: Rails大学
updated_at: "2025-01-01 00:00:00"
created_at: "2025-01-01 00:00:00"
last_activity_at: "2025-01-01 00:00:00"
Comment on lines +1559 to +1577
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

name_kana の形式について確認してください。

フィクスチャの追加自体は問題ありませんが、name_kana が他のユーザーフィクスチャと異なり、カタカナ形式ではなく漢字のまま設定されています。

name: 給付金コースのユーザー
name_kana: 給付金コースのユーザー  # 他のフィクスチャは「キュウフキンコースノユーザー」のようなカタカナ形式

テストデータとしては動作に影響しませんが、一貫性のために修正を検討してください。

♻️ 修正案
-  name_kana: 給付金コースのユーザー
+  name_kana: キュウフキンコースノユーザー
🤖 Prompt for AI Agents
In `@db/fixtures/users.yml` around lines 1559 - 1577, The name_kana field is set
in kanji instead of katakana; update the fixture entry for login_name
grant-course by replacing name_kana: 給付金コースのユーザー with the katakana form (e.g.
name_kana: キュウフキンコースノユーザー) so it matches other user fixtures and maintains
consistency with name: 給付金コースのユーザー.