-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6958 from fjordllc/feature/show-users-divided-by-…
…area 都道府県別ユーザー一覧の追加
- Loading branch information
Showing
37 changed files
with
657 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
class API::Users::AreasController < API::BaseController | ||
def index | ||
tokyo_area_id = '13' | ||
@users = Area.users(params[:region], params[:area] || tokyo_area_id) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
class Users::AreasController < ApplicationController | ||
def index | ||
@number_of_users_by_region = Area.number_of_users_by_region | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React from 'react' | ||
// components | ||
import LoadingListPlaceholder from '../LoadingListPlaceholder' | ||
import EmptyMessage from '../ui/EmptyMessage' | ||
import { UserGroup } from '../ui/UserGroup' | ||
import { MultiColumns } from '../layout/MultiColumns' | ||
// hooks | ||
import { useSearchParams, usePopstate } from '../../hooks/useSearchParams' | ||
import useSWR from 'swr' | ||
// helper | ||
import fetcher from '../../fetcher' | ||
|
||
function RegionCard({ region, numberOfUsersByRegion, onUpdateSelectedArea }) { | ||
return ( | ||
<nav className="page-nav a-card"> | ||
<header className="page-nav__header"> | ||
<h2 className="page-nav__title"> | ||
<span className="page-nav__title-inner">{region}</span> | ||
</h2> | ||
</header> | ||
<hr className="a-border-tint"></hr> | ||
<ul className="page-nav__items"> | ||
{Object.keys(numberOfUsersByRegion).map((area) => ( | ||
<li key={area} className="page-nav__item"> | ||
<button | ||
onClick={() => onUpdateSelectedArea({ region, area })} | ||
className="page-nav__item-link a-text-link"> | ||
{`${area}(${numberOfUsersByRegion[area]})`} | ||
</button> | ||
</li> | ||
))} | ||
</ul> | ||
</nav> | ||
) | ||
} | ||
|
||
/** | ||
* 都道府県を指定しないデフォルトでは東京都が選択されます | ||
*/ | ||
export default function FilterByArea({ numberOfUsersByRegion }) { | ||
const { searchParams, setSearchParams } = useSearchParams({ area: '東京都' }) | ||
const apiUrl = '/api/users/areas?' | ||
const { data: users, error, mutate } = useSWR(apiUrl + searchParams, fetcher) | ||
|
||
const handleUpdateSelectedArea = async ({ region, area }) => { | ||
const search = new URLSearchParams({ region, area }) | ||
const newUsers = await fetcher(apiUrl + search).catch((error) => { | ||
console.error(error) | ||
}) | ||
mutate(newUsers) | ||
setSearchParams(search) | ||
} | ||
|
||
usePopstate(async () => { | ||
const newUsers = await fetcher(apiUrl + searchParams).catch((error) => { | ||
console.error(error) | ||
}) | ||
mutate(newUsers) | ||
}) | ||
|
||
if (error) return <>エラーが発生しました。</> | ||
if (!users) { | ||
return ( | ||
<div className="page-body"> | ||
<div className="container is-md"> | ||
<LoadingListPlaceholder /> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
return ( | ||
<MultiColumns data-testid="areas" isReverse> | ||
{/* region毎に区分されたareaの選択一覧 */} | ||
<MultiColumns.Sub className="is-sm"> | ||
{Object.keys(numberOfUsersByRegion).map((region) => ( | ||
<RegionCard | ||
key={region} | ||
region={region} | ||
numberOfUsersByRegion={numberOfUsersByRegion[region]} | ||
onUpdateSelectedArea={handleUpdateSelectedArea} | ||
/> | ||
))} | ||
</MultiColumns.Sub> | ||
{/* 選択されたareaのユーザー一覧 */} | ||
<MultiColumns.Main> | ||
<section className="a-card"> | ||
{users.length > 0 ? ( | ||
<UserGroup> | ||
<UserGroup.Header> | ||
{searchParams.get('area') || '東京都'} | ||
</UserGroup.Header> | ||
<UserGroup.Icons users={users} /> | ||
</UserGroup> | ||
) : ( | ||
<EmptyMessage>都道府県別ユーザー一覧はありません</EmptyMessage> | ||
)} | ||
</section> | ||
</MultiColumns.Main> | ||
</MultiColumns> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from 'react' | ||
import clsx from 'clsx' | ||
|
||
export const MultiColumns = ({ | ||
className, | ||
children, | ||
isReverse = false, | ||
...props | ||
}) => { | ||
return ( | ||
<div className={clsx('page-body', className)} {...props}> | ||
<div className="container is-lg"> | ||
<div className={clsx('page-body__columns', isReverse && 'is-reverse')}> | ||
{children} | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
const MultiColumnsMain = ({ className, children, ...props }) => { | ||
return ( | ||
<div className={clsx('page-body__column is-main', className)} {...props}> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
const MultiColumnsSub = ({ className, children, ...props }) => { | ||
return ( | ||
<div className={clsx('page-body__column is-sub', className)} {...props}> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
MultiColumns.Main = MultiColumnsMain | ||
MultiColumns.Sub = MultiColumnsSub |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import React from 'react' | ||
|
||
export default function EmptyMessage({ children }) { | ||
return ( | ||
<div className="o-empty-message"> | ||
<div className="o-empty-message__icon"> | ||
<i className="fa-regular fa-smile" /> | ||
</div> | ||
<p className="o-empty-message__text">{children}</p> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import React from 'react' | ||
import clsx from 'clsx' | ||
|
||
export const UserGroup = ({ className, ...props }) => { | ||
return <div className={clsx('user-group', className)} {...props} /> | ||
} | ||
|
||
const UserGroupHeader = ({ className, children, ...props }) => { | ||
return ( | ||
<header className={clsx('user-group__header', className)} {...props}> | ||
<h2 className="user-group__title">{children}</h2> | ||
</header> | ||
) | ||
} | ||
|
||
const UserGroupIcons = ({ users, className, ...props }) => { | ||
return ( | ||
<div className={clsx('a-user-icons', className)} {...props}> | ||
<div className="a-user-icons__items"> | ||
{users.map((user) => ( | ||
<UserIcon user={user} key={user.id} /> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
const UserIcon = ({ user }) => { | ||
const primaryRole = `is-${user.primary_role}` | ||
|
||
return ( | ||
<a className="a-user-icons__item-link" href={user.url}> | ||
<span className={clsx('a-user-role', primaryRole)}> | ||
<img | ||
src={user.avatar_url} | ||
title={user.icon_title} | ||
data-login-name={user.login_name} | ||
className="a-user-icons__item-icon a-user-icon" | ||
/> | ||
</span> | ||
</a> | ||
) | ||
} | ||
|
||
UserGroup.Header = UserGroupHeader | ||
UserGroup.Icons = UserGroupIcons |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// React18にアップデートしたらshimを使うのを辞めてください | ||
import { useSyncExternalStore } from 'use-sync-external-store/shim' | ||
|
||
// Proxyを使ってhistoryのメソッド呼び出しに合わせてイベントを発行する処理を挟みます | ||
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy | ||
// Navigation APIには対応していません | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API | ||
function proxyHistoryMethod(method) { | ||
const handler = { | ||
// applyの関数は関数呼び出し時に実行されます | ||
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply | ||
apply: function (target, thisArg, argumentsList) { | ||
const event = new Event(method.toLowerCase()) | ||
window.dispatchEvent(event) | ||
return Reflect.apply(target, thisArg, argumentsList) | ||
} | ||
} | ||
|
||
history[method] = new Proxy(history[method], handler) | ||
} | ||
|
||
// historyのメソッドをproxyに置き換える | ||
proxyHistoryMethod('pushState') | ||
proxyHistoryMethod('replaceState') | ||
|
||
function subscribe(callback) { | ||
window.addEventListener('pushstate', callback) | ||
window.addEventListener('replacestate', callback) | ||
window.addEventListener('popstate', callback) | ||
return () => { | ||
window.removeEventListener('pushstate', callback) | ||
window.removeEventListener('replacestate', callback) | ||
window.removeEventListener('popstate', callback) | ||
} | ||
} | ||
|
||
/** | ||
* URLが変わる度に再レンダリングを起こして新しいlocationを返すhookです | ||
* レンダリングされる瞬間にlocationを使う(locationの内容が表示に関係している) | ||
* 場合に使って下さい | ||
* @param {(location) => any} [selector=null] - locationの一部分だけ使いたい場合は関数を渡すことも出来ます | ||
* @example | ||
* location.pathnameのみ欲しい場合の例 | ||
* const pathname = useLocation((location) => location.pathname) | ||
* | ||
* @see 参考 https://ja.react.dev/learn/lifecycle-of-reactive-effects#can-global-or-mutable-values-be-dependencies | ||
*/ | ||
function useLocation(selector = (location) => location) { | ||
return useSyncExternalStore(subscribe, () => selector(location)) | ||
} | ||
|
||
export { useLocation } |
Oops, something went wrong.