Skip to content

Commit

Permalink
Merge pull request #20 from /issues/17-feat-member-list-page
Browse files Browse the repository at this point in the history
部員一覧画面を追加
  • Loading branch information
SatooRu65536 authored Dec 26, 2024
2 parents 299e633 + 49d4fa0 commit ce97bac
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 52 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
},
"typescript.preferences.importModuleSpecifier": "non-relative"
}
4 changes: 3 additions & 1 deletion app/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function GlobalStyles() {
'--background-color': '#ffffff',
'--background-color-dark': '#f0f2f5',
'--on-background-color': '#002766',
'--shadow-color': 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
'--shadow-color': 'rgba(0, 0, 0, 1)',

'--fontsize-xs': '0.6rem',
'--fontsize-sm': '0.8rem',
Expand All @@ -31,6 +31,8 @@ export default function GlobalStyles() {
'--radius-lg': '16px',

'--header-height': '50px',

'--shadow': 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
},
'header': {
height: 'var(--header-height)',
Expand Down
32 changes: 32 additions & 0 deletions app/components/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { styled } from 'restyle';

interface Props extends React.HTMLAttributes<HTMLDetailsElement> {
title: string;
children: React.ReactNode;
open?: boolean;
}

const Details = styled('details', {
padding: '10px 0',
});

const Summary = styled('summary', {
userSelect: 'none',
cursor: 'pointer',
});

const Title = styled('h2', {
display: 'inline',
});

export default function Accordion({ title, children, open, ...props }: Props) {
return (
<Details open={open} {...props}>
<Summary>
<Title>{title}</Title>
</Summary>

{children}
</Details>
);
}
94 changes: 94 additions & 0 deletions app/components/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { Member } from '@/types/member';
import { Text } from '@/components/basic';
import { Link } from '@remix-run/react';
import { useMemo } from 'react';
import { styled } from 'restyle';

interface Props {
member: Member['public'];
}

const CardRoot = styled('article', {
'width': '100%',
'boxShadow': '0 0 1px var(--shadow-color)',
'borderRadius': 'var(--radius-md)',
'overflow': 'hidden',

'@media (width <= 500px)': {
display: 'grid',
gridTemplateColumns: 'auto 1fr',
},
});

const CardLink = styled(Link, {
'textDecoration': 'none',
'@media (width <= 500px)': {
display: 'grid',
gridTemplateColumns: 'subgrid',
gridColumn: '1 / -1',
},
});

const AspectRatioStyled = styled('div', {
'overflow': 'hidden',

'@media (width <= 500px)': {
height: '150px',
aspectRatio: '1/1',
},
'@media (width > 500px)': {
aspectRatio: '3/2',
},
});

const CardThumbnail = styled('img', {
width: '100%',
height: '100%',
objectFit: 'cover',
});

const CardBody = styled('div', {
'padding': 10,

'@media (width <= 500px)': {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
});

export default function Card({ member }: Props) {
const title = useMemo(() => {
if (member.type === 'active' && member.position) {
return `${member.lastName} ${member.firstName} (${member.position})`;
}
return `${member.lastName} ${member.firstName}`;
}, [member]);

const subTitle = useMemo(() => {
if (member.type === 'active') {
return `${member.studentId}`;
} else if (member.type === 'alumni') {
return `${member.graduationYear}年卒`;
} else if (member.type === 'external') {
return member.organization;
}
}, [member]);

return (
<CardRoot>
<CardLink to={`/member/${member.uuid}`}>
<AspectRatioStyled>
<CardThumbnail alt={member.slackDisplayName} src={member.iconUrl} />
</AspectRatioStyled>

<CardBody>
<div>
<Text bold size="lg">{title}</Text>
<Text nowrap overflow="hidden" textOverflow="ellipsis">{subTitle}</Text>
</div>
</CardBody>
</CardLink>
</CardRoot>
);
}
2 changes: 1 addition & 1 deletion app/components/Header/Avatar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const PopoverContentStyled = styled(PopoverContent, {
'padding': '10px',
'width': '150px',
'backgroundColor': 'var(--background-color)',
'boxShadow': 'var(--shadow-color)',
'boxShadow': 'var(--shadow)',
'animationDuration': '400ms',
'animationTimingFunction': 'cubic-bezier(0.16, 1, 0.3, 1)',
'willChange': 'transform, opacity',
Expand Down
4 changes: 1 addition & 3 deletions app/components/MemberProperty/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,14 @@ export default function MemberProperty({ editable = true, disabled = false, prop
const valueString = useMemo(() => {
if (rest.type === 'date') {
return dayjs(value).format(rest.format ?? 'YYYY年M月D日');
}
else if (rest.type === 'select') {
} else if (rest.type === 'select') {
return rest.options.find((option) => option.key === value)?.name ?? '';
}

return value.toString();
}, [rest, value]);

const showValueString = !editable;
// const showValueString = !(editable || (!editable && rest.type === 'icon'));
const showInput = editable && rest.type !== 'select';
const showSelect = editable && rest.type === 'select';
const showIcon = rest.type === 'icon';
Expand Down
3 changes: 1 addition & 2 deletions app/components/PublicMemberProperties/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Member } from '@/types/member';
import MemberProperty from '@/components/MemberProperty';
import { TYPES } from '@/consts/member';
import { toTypeName } from '@/utils';
import { styled } from 'restyle';

const SectionTitle = styled('h1', {
Expand Down Expand Up @@ -37,7 +36,7 @@ export default function PublicMemberProperties({ publicInfo, editable }: Props)
<MemberProperty editable={editable} property="名前(カナ)" type="text" value={fullNameKana} />
<MemberProperty editable={editable} property="Slack表示名" type="text" value={slackDisplayName} />
<MemberProperty editable={editable} property="アイコン" type="icon" value={publicInfo.iconUrl} />
<MemberProperty editable={editable} options={TYPES} property="タイプ" type="select" value={toTypeName(publicInfo.type) ?? ''} />
<MemberProperty editable={editable} options={TYPES} property="タイプ" type="select" value={publicInfo.type} />

{
publicInfo.type === 'active' && (
Expand Down
15 changes: 14 additions & 1 deletion app/components/basic/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@ interface Props extends React.ComponentProps<typeof TextBase> {
align?: 'left' | 'center' | 'right';
nowrap?: boolean;
overflow?: 'hidden' | 'visible' | 'scroll' | 'auto';
textOverflow?: 'clip' | 'ellipsis';
}

export default function Text({ children, color, size = 'md', height, bold = false, align, nowrap = false, overflow, ...props }: Props) {
export default function Text({
children,
color,
size = 'md',
height,
bold = false,
align,
nowrap = false,
overflow,
textOverflow,
...props
}: Props) {
const [className, Styles] = css({
color: color ? `var(--${color}-color)` : 'var(--text-color)',
fontSize: `var(--fontsize-${size})`,
Expand All @@ -22,6 +34,7 @@ export default function Text({ children, color, size = 'md', height, bold = fals
lineHeight: height,
height,
overflow,
textOverflow,
});

return (
Expand Down
10 changes: 3 additions & 7 deletions app/pages/ErrorBoundaryPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@ export default function ErrorBoundaryPage({ error, notFoundItem }: Props) {
<Link to="/">ホームへ</Link>
</ErrorPageBase>
);
}
else if (error.status === 500) {
} else if (error.status === 500) {
return (
<ErrorPageBase message="サーバーでエラーが発生しました.しばらくしてから再度お試しください" title="500 Internal Server Error">
<Link to="/">ホームへ</Link>
</ErrorPageBase>
);
}
else {
} else {
return (
<ErrorPageBase message="エラーが発生しました." title={`${error.status} ${error.statusText}`}>
<Link to="/">ホームへ</Link>
</ErrorPageBase>
);
}
}

else if (error instanceof Error) {
} else if (error instanceof Error) {
return (
<div>
<ErrorPageBase message="エラーが発生しました." title="システムエラー">
Expand Down
43 changes: 43 additions & 0 deletions app/pages/MemberListPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Member } from '@/types/member';
import Accordion from '@/components/Accordion';
import Card from '@/components/Card';
import { GRADES } from '@/consts/member';
import { toTypeName } from '@/utils';
import { styled } from 'restyle';

interface Props {
members: Record<string, Array<Member['public']>>;
}

const ORDER = [...GRADES, 'external', 'alumni'];

const Container = styled('div', {
padding: '50px 20px',
});

const Grid = styled('div', {
padding: '20px 20px',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: 20,
});

export default function MemberListPage({ members }: Props) {
const sortedMembers = Object.entries(members).sort(([a], [b]) => ORDER.indexOf(a) - ORDER.indexOf(b));

return (
<Container>
{
sortedMembers.map(([key, member]) => (
<Accordion key={key} open title={toTypeName(key) ?? key}>
<Grid>
{member.map((member) => (
<Card key={member.uuid} member={member} />
))}
</Grid>
</Accordion>
))
}
</Container>
);
}
28 changes: 15 additions & 13 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AuthUser } from '@/services/auth.server';
import type { LinksFunction, LoaderFunctionArgs } from '@remix-run/cloudflare';
import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import AuthUserContext from '@/components/AuthtContext';
import Footer from '@/components/Footer';
import Header from '@/components/Header';
Expand All @@ -15,18 +15,20 @@ import {
useLoaderData,
} from '@remix-run/react';

export const links: LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'anonymous',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
},
];
export function links() {
return [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'anonymous',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
},
];
}

export async function loader({ request, context }: LoaderFunctionArgs) {
const authenticator = getAuthenticator(context.cloudflare.env);
Expand Down
Loading

0 comments on commit ce97bac

Please sign in to comment.