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

generation-users.vueと依存するVueファイルをReactコンポーネントに書き換え #7170

Merged
merged 27 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc86a53
user-activity-counts.vueをReact化
Takuya-Sakai91 Dec 28, 2023
e73162a
user-sns.vueをReact化
Takuya-Sakai91 Dec 29, 2023
632de7c
user-tags.vueをReact化
Takuya-Sakai91 Dec 29, 2023
e975cdc
user-practice-progress.vueをReact化
Takuya-Sakai91 Dec 29, 2023
ae4a1b5
following.vueをReact化
Takuya-Sakai91 Dec 29, 2023
d615ebf
user.vueをReact化
Takuya-Sakai91 Dec 29, 2023
a01e3ae
react-paginateをインストール
Takuya-Sakai91 Jan 2, 2024
3f9cf98
pager.vueをReact化
Takuya-Sakai91 Jan 2, 2024
b7ce9e7
Userコンポーネントの条件と表示名を修正
Takuya-Sakai91 Jan 6, 2024
a22c33a
関数、プロパティ、イベントをVue版に合わせて修正
Takuya-Sakai91 Jan 7, 2024
986b0b3
generation-users.vueをReact化
Takuya-Sakai91 Jan 17, 2024
085b718
不要な記述の削除
Takuya-Sakai91 Jan 18, 2024
6bfe5c9
User.jsxの不足部分を追記
Takuya-Sakai91 Jan 26, 2024
9c71420
期生別ユーザーを表示
Takuya-Sakai91 Feb 15, 2024
fb2802b
不要な空白を削除
Takuya-Sakai91 Feb 15, 2024
fe8402f
UserActivityCounts.jsxを修正
Takuya-Sakai91 Feb 18, 2024
96a550e
Following.jsxを修正
Takuya-Sakai91 Feb 18, 2024
e4bacff
User.jsxの微修正
Takuya-Sakai91 Feb 19, 2024
4afe96a
5期生のユーザを追加しページネーションを確認
Takuya-Sakai91 Feb 25, 2024
7e221df
デザイン崩れ修正
machida Mar 5, 2024
acf95f6
不要ファイルを削除
Takuya-Sakai91 Mar 8, 2024
abadef4
フォロー/アンフォロー機能の修正
Takuya-Sakai91 Mar 9, 2024
a7aa050
ページネーションのURL処理を修正
Takuya-Sakai91 Mar 10, 2024
b1c632b
react-paginateのアンインストール
Takuya-Sakai91 Mar 28, 2024
9151a9a
Pager.jsxの削除
Takuya-Sakai91 Mar 28, 2024
6edb9c8
useStateとuseEffectを使用したデータ取得からuseSWRへの移行
Takuya-Sakai91 Mar 30, 2024
bf53c76
フォローの選択が下のユーザーのカードにかぶってしまうのを修正
machida Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions app/javascript/components/Following.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { useState, useRef } from 'react'
import CSRF from '../csrf.js'

export default function Following({ isFollowing, userId, isWatching }) {
const [following, setFollowing] = useState(isFollowing)
const [watching, setWatching] = useState(isWatching)
const followingDetailsRef = useRef(null)

const followOrChangeFollow = (watch) => {
const params = { id: userId, watch: watch }
const method = following ? 'PATCH' : 'POST'
const url = following ? `/api/followings/${userId}` : `/api/followings`
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
.then((response) => {
if (response.ok) {
setFollowing(true)
setWatching(watch)
} else {
alert('フォロー処理に失敗しました')
}
})
.catch((error) => {
console.warn(error)
})
.finally(() => {
followingDetailsRef.current.open = false
})
}

const unfollow = () => {
const params = { id: userId }
fetch(`/api/followings/${userId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': CSRF.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
.then((response) => {
if (response.ok) {
setFollowing(false)
setWatching(false)
} else {
alert('フォロー処理に失敗しました')
}
})
.catch((error) => {
console.warn(error)
})
.finally(() => {
followingDetailsRef.current.open = false
})
}

return (
<details ref={followingDetailsRef} className="following">
<summary className="following__summary">
{following && watching ? (
<span className="a-button is-warning is-sm is-block">
<i className="fa-solid fa-check"></i>
<span>コメントあり</span>
</span>
) : following && !watching ? (
<span className="a-button is-warning is-sm is-block">
<i className="fa-solid fa-check"></i>
<span>コメントなし</span>
</span>
) : (
<span className="a-button is-secondary is-sm is-block">
フォローする
</span>
)}
</summary>
<div className="following__dropdown a-dropdown">
<ul className="a-dropdown__items">
<li className="following__dropdown-item a-dropdown__item">
{following && watching ? (
<button className="following-option a-dropdown__item-inner is-active">
<div className="following-option__inner">
<div className="following-option__label">コメントあり</div>
<div className="following-option__desciption">
フォローしたユーザーの日報を自動でWatch状態にします。日報投稿時の通知と日報にコメントが来た際に通知を受け取ります。
</div>
</div>
</button>
) : (
<button
className="following-option a-dropdown__item-inner"
onClick={() => followOrChangeFollow(true)}>
<div className="following-option__inner">
<div className="following-option__label">コメントあり</div>
<div className="following-option__desciption">
フォローしたユーザーの日報を自動でWatch状態にします。日報投稿時の通知と日報にコメントが来た際に通知を受け取ります。
</div>
</div>
</button>
)}
</li>
<li className="following__dropdown-item a-dropdown__item">
{following && !watching ? (
<button className="following-option a-dropdown__item-inner is-active">
<div className="following-option__inner">
<div className="following-option__label">コメントなし</div>
<div className="following-option__desciption">
フォローしたユーザーの日報はWatch状態にしません。日報投稿時の通知だけ通知を受けとります。
</div>
</div>
</button>
) : (
<button
className="following-option a-dropdown__item-inner"
onClick={() => followOrChangeFollow(false)}>
<div className="following-option__inner">
<div className="following-option__label">コメントなし</div>
<div className="following-option__desciption">
フォローしたユーザーの日報はWatch状態にしません。日報投稿時の通知だけ通知を受けとります。
</div>
</div>
</button>
)}
</li>
<li className="following__dropdown-item a-dropdown__item">
{!following && !watching ? (
<button className="following-option a-dropdown__item-inner is-active">
<div className="following-option__inner">
<div className="following-option__label">フォローしない</div>
</div>
</button>
) : (
<button
className="following-option a-dropdown__item-inner"
onClick={unfollow}>
<div className="following-option__inner">
<div className="following-option__label">フォローしない</div>
</div>
</button>
)}
</li>
</ul>
</div>
</details>
)
}
64 changes: 64 additions & 0 deletions app/javascript/components/GenerationUsers.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react'
import useSWR from 'swr'
import User from './User.jsx'
import Pagination from './Pagination'
import usePage from './hooks/usePage'
import fetcher from '../fetcher'

export default function GenerationUsers({ generationID }) {
const itemsPerPage = 24
const { page, setPage } = usePage()
const { data, error } = useSWR(
`/api/generations/${generationID}.json?page=${page}&per=${itemsPerPage}`,
fetcher
)

if (error) return <>An error has occurred.</>
if (!data) return <>Loading...</>

return (
<div className="page-body">
{data.totalPages > 1 && (
<Pagination
sum={data.totalPages * itemsPerPage}
per={itemsPerPage}
page={page}
setPage={setPage}
/>
)}
<div className="container">
<div className="users row">
{data.users === null ? (
<div className="empty">
<i className="fa-solid fa-spinner fa-pulse" />
ロード中
</div>
) : data.users.length !== 0 ? (
data.users.map((user) => (
<User key={user.id} user={user} currentUser={data.currentUser} />
))
) : (
<div className="row">
<div className="o-empty-message">
<div className="o-empty-message__icon">
<i className="fa-regular fa-smile"></i>
</div>
<p className="o-empty-message_text">
{generationID}期のユーザー一覧はありません
</p>
</div>
</div>
)}
</div>
</div>
{data.totalPages > 1 && (
<Pagination
sum={data.totalPages * itemsPerPage}
per={itemsPerPage}
page={page}
setPage={setPage}
/>
)}
</div>
)
}
168 changes: 168 additions & 0 deletions app/javascript/components/User.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React from 'react'
import Following from './Following.jsx'
import UserActivityCounts from './UserActivityCounts.jsx'
import UserSns from './UserSns.jsx'
import UserTags from './UserTags.jsx'
import UserPracticeProgress from './UserPracticeProgress.jsx'

export default function User({ user, currentUser }) {
const userDescParagraphs = () => {
let description = user.description
description =
description.length <= 200
? description
: description.substring(0, 200) + '...'
const paragraphs = description.split(/\n|\r\n/).map((text, i) => {
return {
id: i,
text: text
}
})
return paragraphs
}

const roleClass = () => `is-${user.primary_role}`

return (
<div className="col-xxl-3 col-xl-4 col-lg-4 col-md-6 col-xs-12">
<div className="users-item">
<div className={`users-item__inner a-card ${roleClass()}`}>
{currentUser &&
(currentUser.mentor || currentUser.admin) &&
user.student_or_trainee && (
<div className="users-item__inactive-message-container is-only-mentor">
{user.roles.includes('retired') && (
<div className="users-item__inactive-message">
退会しました
</div>
)}
{user.roles.includes('hibernationed') && (
<div className="users-item__inactive-message">
休会中: {user.hibernated_at}〜(
{user.hibernation_elapsed_days}日経過)
</div>
)}
{!user.active && (
<div className="users-item__inactive-message">
1ヶ月以上ログインがありません
</div>
)}
</div>
)}
<header className="users-item__header">
<div className="users-item__header-inner">
<div className="users-item__header-start">
<div className="users-item__icon">
<a href={user.url}>
<span className={`a-user-role ${roleClass()}`}>
<img
className="users-item__user-icon-image a-user-icon"
title={user.icon_title}
alt={user.icon_title}
src={user.avatar_url}
/>
</span>
</a>
</div>
</div>
<div className="users-item__header-end">
<div className="card-list-item__rows">
<div className="card-list-item__row">
<div className="card-list-item-title">
<a
className="card-list-item-title__title is-lg a-text-link"
href={user.url}>
{user.login_name}
</a>
{user.company && user.company.logo_url && (
<a href={user.company.url}>
<img
className="user-item__company-logo"
src={user.company.logo_url}
/>
</a>
)}
</div>
</div>
<div className="card-list-item__row">
<div className="card-list-item-meta">
<div className="card-list-item-meta__items">
<div className="card-list-item-meta__item">
<div className="a-meta">{user.name}</div>
</div>
<div className="card-list-item-meta__item">
{user.discord_profile.times_url ? (
<a
className="a-meta"
href={user.discord_profile.times_url}>
<i className="fa-brands fa-discord"></i>
{user.discord_profile.account_name}
</a>
) : (
<div className="a-meta">
<i className="fa-brands fa-discord"></i>
{user.discord_profile.account_name}
</div>
)}
</div>
</div>
</div>
</div>
</div>
<UserSns user={user} />
</div>
</div>
<UserActivityCounts user={user} />
</header>
<div className="users-item__body">
<div className="users-item__description a-short-text">
{userDescParagraphs().map((paragraph) => (
<p key={paragraph.id}>{paragraph.text}</p>
))}
</div>
<div className="users-item__tags">
<UserTags user={user} />
</div>
</div>
<UserPracticeProgress user={user} />
<hr className="a-border-tint" />
<footer className="card-footer users-item__footer">
<div className="card-main-actions">
<ul className="card-main-actions__items">
{currentUser.id !== user.id &&
currentUser.adviser &&
user.company &&
currentUser.company_id === user.company.id && (
<li className="card-main-actions__item">
<div className="a-button is-disabled is-sm is-block">
<i className="fa-solid fa-check"></i>
<span>自社研修生</span>
</div>
</li>
)}
{currentUser.id !== user.id && (
<li className="card-main-actions__item">
<Following
isFollowing={user.isFollowing}
userId={user.id}
isWatching={user.isWatching}
/>
</li>
)}
{currentUser.admin && user.talkUrl && (
<li className="card-main-actions__item is-only-mentor">
<a
className="a-button is-secondary is-sm is-block"
href={user.talkUrl}>
相談部屋
</a>
</li>
)}
</ul>
</div>
</footer>
</div>
</div>
</div>
)
}
Loading