Skip to content

Commit 5b43b92

Browse files
committed
Generated individual complex page
1 parent ea3b2cd commit 5b43b92

4 files changed

Lines changed: 186 additions & 0 deletions

File tree

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
22
import ComplexesPage from './pages/ComplexesPage';
33
import ComplexFormPage from './pages/ComplexFormPage';
4+
import ComplexDetailPage from './pages/ComplexDetailPage';
45
// import ComplexDetailPage from './pages/ComplexDetailPage'; // 将来的に
56
// import ComplexFormPage from './pages/ComplexFormPage'; // 将来的に
67

@@ -10,6 +11,7 @@ function App() {
1011
<Routes>
1112
<Route path="/" element={<ComplexesPage />} />
1213
<Route path="/complexes/new" element={<ComplexFormPage />} />
14+
<Route path="/complexes/:complexId" element={<ComplexDetailPage />} />
1315
{/* 他のルートもここに追加 */}
1416
{/* <Route path="/complexes/:id/edit" element={<ComplexFormPage mode="edit" />} /> */}
1517
{/* <Route path="/complexes/:id" element={<ComplexDetailPage />} /> */}

frontend/src/components/complexes/molecules/ComplexDisplayCard.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import styled from 'styled-components';
33
import { useTranslation } from 'react-i18next';
44
import { gsap } from 'gsap';
55
import type { Complex } from '../../../types/complex';
6+
import Button from '../../common/atoms/Button';
67

78
const CardWrapper = styled.div`
89
/* 映画館のようなダイナミックなデザイン */
@@ -37,14 +38,25 @@ const CardWrapper = styled.div`
3738
}
3839
`;
3940

41+
const DetailsButton = styled(Button)`
42+
position: absolute;
43+
bottom: 2rem;
44+
left: 50%;
45+
transform: translateX(-50%);
46+
background-color: rgba(255, 255, 255, 0.1);
47+
backdrop-filter: blur(5px);
48+
`;
49+
4050
interface ComplexDisplayCardProps {
4151
complex: Complex;
4252
scrollProgress: number; // スクロール進捗を受け取る
53+
onViewDetails: (id: number) => void;
4354
}
4455

4556
const ComplexDisplayCard: React.FC<ComplexDisplayCardProps> = ({
4657
complex,
4758
scrollProgress,
59+
onViewDetails,
4860
}) => {
4961
const { t } = useTranslation();
5062
const h2Ref = useRef<HTMLHeadingElement>(null);
@@ -73,6 +85,12 @@ const ComplexDisplayCard: React.FC<ComplexDisplayCardProps> = ({
7385
{t('categoryLabel')}: {complex.category}
7486
</p>
7587
</p>
88+
<DetailsButton
89+
variant="secondary"
90+
onClick={() => onViewDetails(complex.id)}
91+
>
92+
{t('viewDetails')}
93+
</DetailsButton>
7694
</CardWrapper>
7795
);
7896
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React from 'react';
2+
import { useParams, useNavigate } from 'react-router-dom';
3+
import { useQuery } from '@tanstack/react-query';
4+
import styled from 'styled-components';
5+
import { useTranslation } from 'react-i18next';
6+
import { fetchComplex } from '../services/api';
7+
import type { Complex, Goal } from '../types/complex';
8+
import Header from '../components/common/molecules/Header';
9+
import Button from '../components/common/atoms/Button';
10+
11+
const PageWrapper = styled.div`
12+
padding: 8rem 2rem 2rem;
13+
max-width: 800px;
14+
margin: 0 auto;
15+
`;
16+
17+
const ComplexHeader = styled.div`
18+
margin-bottom: 2rem;
19+
padding-bottom: 1.5rem;
20+
border-bottom: 1px solid #e0e0e0;
21+
`;
22+
23+
const ComplexContent = styled.h1`
24+
font-size: 2.5rem;
25+
font-weight: 700;
26+
color: #1d1d1f;
27+
margin-bottom: 0.5rem;
28+
`;
29+
30+
const ComplexCategory = styled.p`
31+
font-size: 1rem;
32+
color: #58585b;
33+
background-color: #f5f5f7;
34+
display: inline-block;
35+
padding: 0.25rem 0.75rem;
36+
border-radius: 0.5rem;
37+
`;
38+
39+
const GoalsSection = styled.section`
40+
margin-top: 2rem;
41+
`;
42+
43+
const SectionTitle = styled.h2`
44+
font-size: 1.75rem;
45+
font-weight: 600;
46+
color: #1d1d1f;
47+
margin-bottom: 1.5rem;
48+
`;
49+
50+
const GoalCard = styled.div`
51+
background-color: #ffffff;
52+
border-radius: 1rem;
53+
padding: 1.5rem;
54+
margin-bottom: 1.5rem;
55+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
56+
`;
57+
58+
const GoalTitle = styled.h3`
59+
font-size: 1.1rem;
60+
font-weight: 600;
61+
color: #333;
62+
margin-bottom: 0.25rem;
63+
`;
64+
65+
const GoalContent = styled.p`
66+
font-size: 1.25rem;
67+
color: #1d1d1f;
68+
margin-bottom: 1rem;
69+
line-height: 1.5;
70+
`;
71+
72+
const ActionsButton = styled(Button)`
73+
margin-top: 1rem;
74+
`;
75+
76+
const LoadingOrErrorWrapper = styled.div`
77+
display: flex;
78+
justify-content: center;
79+
align-items: center;
80+
height: 50vh;
81+
font-size: 1.5rem;
82+
`;
83+
84+
const ComplexDetailPage: React.FC = () => {
85+
const { complexId } = useParams<{ complexId: string }>();
86+
const navigate = useNavigate();
87+
const { t } = useTranslation();
88+
89+
const {
90+
data: complex,
91+
isLoading,
92+
error,
93+
} = useQuery<Complex, Error>({
94+
queryKey: ['complex', complexId],
95+
queryFn: () => {
96+
if (!complexId) {
97+
throw new Error('Complex ID is missing');
98+
}
99+
return fetchComplex(complexId);
100+
},
101+
enabled: !!complexId, // Only run query if complexId is available
102+
});
103+
104+
if (isLoading) {
105+
return <LoadingOrErrorWrapper>{t('loading')}</LoadingOrErrorWrapper>;
106+
}
107+
108+
if (error || !complex) {
109+
return (
110+
<LoadingOrErrorWrapper>
111+
{error ? `${t('errorPrefix')}${error.message}` : t('complexNotFound')}
112+
</LoadingOrErrorWrapper>
113+
);
114+
}
115+
116+
return (
117+
<>
118+
<Header onAddNewComplex={() => navigate('/complexes/new')} />
119+
<PageWrapper>
120+
<ComplexHeader>
121+
<ComplexContent>{complex.content}</ComplexContent>
122+
<ComplexCategory>{complex.category}</ComplexCategory>
123+
</ComplexHeader>
124+
125+
<Button
126+
variant="secondary"
127+
onClick={() => navigate(`/complexes/${complex.id}/edit`)}
128+
>
129+
{t('editComplexAndGoals')}
130+
</Button>
131+
132+
<GoalsSection>
133+
<SectionTitle>{t('relatedGoals')}</SectionTitle>
134+
{complex.goals && complex.goals.length > 0 ? (
135+
complex.goals.map((goal: Goal) => (
136+
<GoalCard key={goal.id}>
137+
<GoalTitle>{t('surfaceGoal')}</GoalTitle>
138+
<GoalContent>{goal.surface_goal}</GoalContent>
139+
<GoalTitle>{t('underlyingGoal')}</GoalTitle>
140+
<GoalContent>{goal.underlying_goal}</GoalContent>
141+
<ActionsButton
142+
onClick={() => navigate(`/goals/${goal.id}/actions`)}
143+
>
144+
{t('trackActions')}
145+
</ActionsButton>
146+
</GoalCard>
147+
))
148+
) : (
149+
<p>{t('noGoalsSet')}</p>
150+
)}
151+
</GoalsSection>
152+
153+
<Button onClick={() => navigate('/')} style={{ marginTop: '2rem' }}>
154+
{t('backToList')}
155+
</Button>
156+
</PageWrapper>
157+
</>
158+
);
159+
};
160+
161+
export default ComplexDetailPage;

frontend/src/pages/ComplexesPage.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ const ComplexesPage: React.FC = () => {
123123
} = useInfiniteCircularScroll({ items: complexes, loopAround: true });
124124

125125
// TODO: Complex編集画面に実装する
126+
const handleViewDetails = (id: number) => {
127+
navigate(`/complexes/${id}`);
128+
};
129+
126130
// 削除ミューテーション
127131
// const deleteMutation = useMutation<void, Error, number>({
128132
// mutationFn: deleteComplex,
@@ -190,6 +194,7 @@ const ComplexesPage: React.FC = () => {
190194
<ComplexDisplayCard
191195
complex={complex}
192196
scrollProgress={scrollProgress}
197+
onViewDetails={handleViewDetails}
193198
/>
194199
</ComplexScrollItem>
195200
))}

0 commit comments

Comments
 (0)