Skip to content

Commit 83248dc

Browse files
committed
Card表面をV2に対応
1 parent 0f75874 commit 83248dc

File tree

5 files changed

+141
-76
lines changed

5 files changed

+141
-76
lines changed

web/app/home/page.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { motion, useAnimation } from "framer-motion";
66
import { useCallback, useEffect, useState } from "react";
77
import { MdThumbUp } from "react-icons/md";
88
import request from "~/api/request";
9-
import { useMyID, useRecommended } from "~/api/user";
9+
import { useAboutMe, useRecommended } from "~/api/user";
1010
import { Card } from "~/components/Card";
1111
import { DraggableCard } from "~/components/DraggableCard";
1212
import FullScreenCircularProgress from "~/components/common/FullScreenCircularProgress";
@@ -17,8 +17,8 @@ export default function Home() {
1717
const controls = useAnimation();
1818
const [clickedButton, setClickedButton] = useState<string>("");
1919
const {
20-
state: { data: myId },
21-
} = useMyID();
20+
state: { data: currentUser },
21+
} = useAboutMe();
2222

2323
const [_, rerender] = useState({});
2424
const [recommended, setRecommended] = useState<
@@ -71,6 +71,9 @@ export default function Home() {
7171
});
7272
}, [controls, accept]);
7373

74+
if (currentUser == null) {
75+
return <FullScreenCircularProgress />;
76+
}
7477
if (recommended == null) {
7578
return <FullScreenCircularProgress />;
7679
}
@@ -89,15 +92,15 @@ export default function Home() {
8992
{nextUser && (
9093
<div className="relative h-full w-full">
9194
<div className="-translate-x-4 -translate-y-4 inset-0 z-0 mt-4 transform">
92-
<Card displayedUser={nextUser} />
95+
<Card displayedUser={nextUser} currentUser={currentUser} />
9396
</div>
9497
<motion.div
9598
animate={controls}
9699
className="absolute inset-0 z-10 mt-4 flex items-center justify-center"
97100
>
98101
<DraggableCard
99102
displayedUser={displayedUser}
100-
comparisonUserId={myId || undefined}
103+
currentUser={currentUser}
101104
onSwipeLeft={reject}
102105
onSwipeRight={accept}
103106
clickedButton={clickedButton}

web/app/settings/profile/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export default function SettingsProfile() {
4242
編集する
4343
</Link>
4444
</div>
45-
<Card displayedUser={data} onFlip={(back) => setBack(back)} />
45+
<Card
46+
displayedUser={data}
47+
currentUser={data}
48+
onFlip={(back) => setBack(back)}
49+
/>
4650
</div>
4751
</div>
4852
)}

web/components/Card.tsx

Lines changed: 119 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,191 @@
1-
import React, { useState, useRef, useEffect } from "react";
21
import ThreeSixtyIcon from "@mui/icons-material/ThreeSixty";
3-
import type { User, UserID } from "common/types";
4-
import { Chip } from "@mui/material";
5-
import type { UserID, UserWithCoursesAndSubjects } from "common/types";
6-
import { useState } from "react";
2+
import type { UserWithCoursesAndSubjects } from "common/types";
3+
import React, { useState, useRef, useEffect, useCallback } from "react";
74
import NonEditableCoursesTable from "./course/NonEditableCoursesTable";
85
import UserAvatar from "./human/avatar";
9-
import NonEditableCoursesTable from "./course/NonEditableCoursesTable";
106

117
interface CardProps {
128
displayedUser: UserWithCoursesAndSubjects;
13-
comparisonUserId?: UserID;
9+
currentUser: UserWithCoursesAndSubjects;
1410
onFlip?: (isBack: boolean) => void;
1511
}
1612

17-
const interests = [
18-
"記号論理学",
19-
"量子力学",
20-
"離散数学",
21-
"プログラミング",
22-
"量子情報理論",
23-
"オペレーションズリサーチ",
24-
];
25-
26-
const CardFront = ({ displayedUser }: { displayedUser: User }) => {
13+
const CardFront = ({ displayedUser, currentUser }: CardProps) => {
2714
const containerRef = useRef<HTMLDivElement>(null);
15+
const interestsContainerRef = useRef<HTMLDivElement>(null);
16+
const coursesContainerRef = useRef<HTMLDivElement>(null);
2817
const [isHiddenInterestExist, setHiddenInterestExist] = useState(false);
18+
const [isHiddenCourseExist, setHiddenCourseExist] = useState(false);
2919

3020
useEffect(() => {
3121
const container = containerRef.current;
3222
if (!container) return;
3323

3424
const resizeObserver = new ResizeObserver(() => {
3525
calculateVisibleInterests();
26+
calculateVisibleCourses();
3627
});
3728

3829
resizeObserver.observe(container);
3930

4031
calculateVisibleInterests(); // 初期計算
32+
calculateVisibleCourses(); // 初期計算
4133

4234
return () => resizeObserver.disconnect();
4335
}, []);
4436

45-
const calculateVisibleInterests = () => {
46-
const container = containerRef.current;
37+
const calculateVisibleCourses = useCallback(() => {
38+
const courses = displayedUser.courses;
39+
const container = coursesContainerRef.current;
40+
if (!container) return;
41+
42+
const containerHeight = container.offsetHeight; // コンテナの高さを取得
43+
44+
// 一旦コンテナを初期化
45+
container.innerHTML = "";
46+
setHiddenCourseExist(false);
47+
48+
// courses を一致・非一致で分類
49+
const matchingCourses = courses.filter((course) =>
50+
currentUser.courses.some((c) => c.id === course.id),
51+
);
52+
const nonMatchingCourses = courses.filter(
53+
(course) => !currentUser.courses.some((c) => c.id === course.id),
54+
);
55+
56+
// courses を表示する flex コンテナ
57+
const coursesContainer = document.createElement("div");
58+
coursesContainer.classList.add("flex", "flex-wrap", "gap-2");
59+
container.appendChild(coursesContainer);
60+
61+
// 一致しているコースを先に表示
62+
for (const course of [...matchingCourses, ...nonMatchingCourses]) {
63+
const isMatching = currentUser.courses.some((c) => c.id === course.id);
64+
65+
// 新しい div 要素を作成
66+
const element = document.createElement("div");
67+
element.textContent = course.name;
68+
69+
// スタイル適用(赤 or 灰色)
70+
element.classList.add("badge", "badge-outline");
71+
element.style.backgroundColor = isMatching ? "red" : "gray";
72+
element.style.color = "white";
73+
74+
// 表示判定
75+
if (coursesContainer.offsetHeight + 30 <= containerHeight) {
76+
coursesContainer.appendChild(element);
77+
} else {
78+
setHiddenCourseExist;
79+
}
80+
}
81+
}, [displayedUser, currentUser]);
82+
83+
const calculateVisibleInterests = useCallback(() => {
84+
const interests = displayedUser.interestSubjects;
85+
const container = interestsContainerRef.current;
4786
if (!container) return;
4887

49-
const containerHeight = container.offsetHeight; // ここで高さを取得
88+
const containerHeight = container.offsetHeight; // コンテナの高さを取得
5089

51-
// 一旦全てのバッジを非表示にする
90+
// 一旦コンテナを初期化
5291
container.innerHTML = "";
5392
setHiddenInterestExist(false);
5493

55-
// interestsを入れるflexコンテナ
94+
// interests を一致・非一致で分類
95+
const matchingInterests = interests.filter((interest) =>
96+
currentUser.interestSubjects.some((i) => i.name === interest.name),
97+
);
98+
const nonMatchingInterests = interests.filter(
99+
(interest) =>
100+
!currentUser.interestSubjects.some((i) => i.name === interest.name),
101+
);
102+
103+
// interests を表示する flex コンテナ
56104
const flexContainer = document.createElement("div");
57105
flexContainer.classList.add("flex", "flex-wrap", "gap-2");
58106
container.appendChild(flexContainer);
59107

60-
// interests配列をループしてバッジを作成
61-
interests.forEach((interest) => {
108+
// 一致している興味分野を先に表示
109+
for (const interest of [...matchingInterests, ...nonMatchingInterests]) {
110+
const isMatching = currentUser.interestSubjects.some(
111+
(i) => i.name === interest.name,
112+
);
113+
62114
// 新しい div 要素を作成
63115
const element = document.createElement("div");
64-
element.textContent = interest;
116+
element.textContent = interest.name;
65117

66-
// スタイルを適用
118+
// スタイル適用(赤 or 灰色)
67119
element.classList.add("badge", "badge-outline");
120+
element.style.backgroundColor = isMatching ? "red" : "gray";
121+
element.style.color = "white";
68122
element.style.overflow = "hidden";
69123
element.style.whiteSpace = "nowrap";
70124
element.style.textOverflow = "ellipsis";
71-
element.style.display = "inline-block";
72125

73-
// 要素がコンテナの高さを超えていない場合、表示
74-
if (flexContainer.offsetHeight + 20 <= containerHeight) {
126+
// 表示判定
127+
if (flexContainer.offsetHeight + 30 <= containerHeight) {
75128
flexContainer.appendChild(element);
76129
} else {
77130
setHiddenInterestExist(true);
78131
}
79-
});
80-
81-
};
132+
}
133+
}, [displayedUser, currentUser]);
82134

83135
return (
84-
<div className="flex h-full flex-col justify-between gap-5 overflow-clip border-2 border-primary bg-secondary p-5">
136+
<div className="flex h-full flex-col gap-5 overflow-clip border-2 border-primary bg-secondary p-5">
85137
<div className="grid h-[20%] grid-cols-3 items-center">
86-
<UserAvatar pictureUrl={displayedUser.pictureUrl} width="9dvh" height="9dvh" />
87-
<div className="grid grid-rows-3 items-center col-span-2">
138+
<UserAvatar
139+
pictureUrl={displayedUser.pictureUrl}
140+
width="9dvh"
141+
height="9dvh"
142+
/>
143+
<div className="col-span-2 grid grid-rows-3 items-center">
88144
<p className="col-span-3 font-bold text-1xl">{displayedUser.name}</p>
89-
<p className="col-span-3 text-1xl">{displayedUser.grade}</p>
90-
<p className="col-span-1 text-1xl">{displayedUser.faculty}</p>
145+
<p className="col-span-1 text-1xl">{displayedUser.grade}</p>
146+
<p className="col-span-2 text-1xl">{displayedUser.faculty}</p>
91147
<p className="col-span-2 text-1xl">{displayedUser.department}</p>
92148
</div>
93149
</div>
94150

95-
<div ref={containerRef} className="h-[50%] overflow-hidden width-full">
96-
<div>
151+
<div className="flex h-[70%] w-full flex-col gap-2" ref={containerRef}>
152+
<div
153+
ref={interestsContainerRef}
154+
className="width-full h-[50%] overflow-hidden"
155+
>
156+
<div />
157+
{isHiddenInterestExist && (
158+
<div className="badge badge-outline bg-gray-200 text-gray-700">
159+
And More
160+
</div>
161+
)}
97162
</div>
98-
</div>
99163

100-
{isHiddenInterestExist && (
101-
<div className="badge badge-outline bg-gray-200 text-gray-700">
102-
And More
164+
<div
165+
ref={coursesContainerRef}
166+
className="width-full h-[50%] overflow-hidden"
167+
>
168+
<div />
169+
{isHiddenCourseExist && (
170+
<div className="badge badge-outline bg-gray-200 text-gray-700">
171+
And More
172+
</div>
173+
)}
103174
</div>
104-
)}
105-
106-
<div className="flex justify-center">
107-
<ThreeSixtyIcon className="text-3xl" />
108175
</div>
109176
</div>
110177
);
111178
};
112179

113-
const CardBack = ({ displayedUser, comparisonUserId }: CardProps) => {
180+
const CardBack = ({ displayedUser, currentUser }: CardProps) => {
114181
return (
115182
<div className="flex h-full flex-col overflow-hidden border-2 border-primary bg-secondary p-4">
116183
<div className="flex justify-center">
117184
<p className="font-bold text-lg">{displayedUser?.name}</p>
118185
</div>
119186
<NonEditableCoursesTable
120187
userId={displayedUser.id}
121-
comparisonUserId={comparisonUserId}
188+
comparisonUserId={currentUser.id}
122189
/>
123190
<div className="mt-4 flex justify-center">
124191
<ThreeSixtyIcon className="text-3xl" />
@@ -127,7 +194,7 @@ const CardBack = ({ displayedUser, comparisonUserId }: CardProps) => {
127194
);
128195
};
129196

130-
export function Card({ displayedUser, comparisonUserId, onFlip }: CardProps) {
197+
export function Card({ displayedUser, currentUser, onFlip }: CardProps) {
131198
const [isDisplayingBack, setIsDisplayingBack] = useState(false);
132199

133200
const handleRotate = () => {
@@ -155,7 +222,7 @@ export function Card({ displayedUser, comparisonUserId, onFlip }: CardProps) {
155222
transform: isDisplayingBack ? "rotateY(180deg)" : "rotateY(0deg)",
156223
}}
157224
>
158-
<CardFront displayedUser={displayedUser} />
225+
<CardFront displayedUser={displayedUser} currentUser={currentUser} />
159226
</div>
160227
<div
161228
className="absolute h-full w-full"
@@ -164,10 +231,7 @@ export function Card({ displayedUser, comparisonUserId, onFlip }: CardProps) {
164231
transform: isDisplayingBack ? "rotateY(0deg)" : "rotateY(-180deg)",
165232
}}
166233
>
167-
<CardBack
168-
displayedUser={displayedUser}
169-
comparisonUserId={comparisonUserId}
170-
/>
234+
<CardBack displayedUser={displayedUser} currentUser={currentUser} />
171235
</div>
172236
</div>
173237
</div>

web/components/DraggableCard.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import CloseIcon from "@mui/icons-material/Close";
22
import { Box, Typography } from "@mui/material";
3-
import type { UserID, UserWithCoursesAndSubjects } from "common/types";
3+
import type { UserWithCoursesAndSubjects } from "common/types";
44
import { motion, useMotionValue, useMotionValueEvent } from "framer-motion";
55
import { useCallback, useState } from "react";
66
import { MdThumbUp } from "react-icons/md";
@@ -10,15 +10,15 @@ const SWIPE_THRESHOLD = 30;
1010

1111
interface DraggableCardProps {
1212
displayedUser: UserWithCoursesAndSubjects;
13-
comparisonUserId?: UserID;
13+
currentUser: UserWithCoursesAndSubjects;
1414
onSwipeRight: () => void;
1515
onSwipeLeft: () => void;
1616
clickedButton: string;
1717
}
1818

1919
export const DraggableCard = ({
2020
displayedUser,
21-
comparisonUserId,
21+
currentUser,
2222
onSwipeRight,
2323
onSwipeLeft,
2424
clickedButton,
@@ -136,10 +136,7 @@ export const DraggableCard = ({
136136
whileTap={{ scale: 0.95 }}
137137
>
138138
<CardOverlay />
139-
<Card
140-
displayedUser={displayedUser}
141-
comparisonUserId={comparisonUserId}
142-
/>
139+
<Card displayedUser={displayedUser} currentUser={currentUser} />
143140
</motion.div>
144141
</section>
145142
</div>

0 commit comments

Comments
 (0)